Bora falar sobre funções HASH? #first

Pedro Matias de Araujo
devorando
Published in
9 min readJan 28, 2020

--

o primeiro artigo do devorando e uma visão geral de teoria e segurança!

Olá pessoal, eu sou o Pedro, desenvolvedor full stack aqui no aiqfome. E vou estrear com vocês o devorando, o blog dos devs do app mais fominha da internê!

Aviso aos navegantes: esse papo aqui é de dev para dev, morô? Então vão aparecer muitos termos técnicos. Se ficar difícil e você não souber algum, pare o texto, pesquise o que significa e aí volte pra cá! 💜

Vocês devem estar pensando, caramba cara, vai falar de funções HASH logo de cara???? A resposta é sim, porque vou falar um monte sobre criptografia/segurança e preciso de uma base pra isso, pra conseguir avançar sem precisar explicar o que é hash em cada post, belezera? Então vamos à parte técnica:

A definição mais comum para funções hash seria “uma função que mapeia uma string de bits de comprimento arbitrário para uma string de bits de comprimento fixo”. Ou seja, ele pega a sua entrada, que pode ser um texto, um pdf, um número… o que for, e transforma num texto de tamanho fixo, “aleatoriamente”.

Uma função hash é unidirecional, ou seja, uma função sobrejetora que não permite uma função inversa. (Lembra quando cê perguntava pra fessora onde você ia usar função sobrejetora? Tá aí!)

Logo, é fácil calcular o valor da hash a partir de uma entrada qualquer de comprimento arbitrário. Em contrapartida, é computacionalmente difícil retornar ao valor anterior da mensagem a partir de um dado valor da hash.

Propriedades Hash

Para uma aplicação criptográfica, há três propriedades desejáveis:

Resistência à colisão

É computacionalmente inviável encontrar qualquer par (x, y) tal que H(x)=H(y)

Resistência à primeira inversão

Para qualquer valor h dado, é computacionalmente inviável encontrar x tal que H(x)=h.

Resistência à segunda inversão

Para qualquer bloco de dados x, é computacionalmente inviável encontrar y diferente de x, tal que H(y)=H(x).

A situação ideal é que toda hash gerada seja realmente única para cada valor de entrada, mas isso não é possível. Nas funções hash, os valores de hash possíveis de serem geradas são finitas, pois têm um tamanho fixo de saída e como o tamanho da entrada é teoricamente infinita, pelo Princípio da Casa dos Pombos, haverá colisões com duas entradas distintas, resultando na mesma hash.

Porém, essas funções foram construídas para minimizar colisões válidas, ou seja, é muito improvável que duas mensagens, que possuem sentido no mesmo contexto, resultem na mesma hash.

Exemplos de uso de hash

As funções de hash têm uma grande variedade de usos em segurança, tais como:

Autenticação de mensagens

Mecanismo ou serviço usado para verificar a integridade de uma mensagem; permite assegurar que a informação recebida seja igual à mensagem enviada (sem modificação, inserção, supressão ou replay);

Assinaturas digitais

O valor de resumo de uma mensagem é cifrado com a chave privada do emissor, qualquer usuário que possua a chave pública pode verificar a integridade da mensagem que é associada com a assinatura digital;

Arquivo de senhas

Um resumo de uma senha fica armazenado em um arquivo do sistema operacional em vez de se armazenar a senha propriamente dita, caso o arquivo de senha seja violado, o atacante só conseguirá obter a hash da senha;

Detecção de intrusos e vírus

Para cada arquivo F do sistema, armazena-se também a hash H(F), se houver qualquer alteração em F, esta será percebida;

Construção de funções pseudoaleatórias (PRF) ou construção de gerador de números pseudoaleatórios (PRNG)

Para geração de chaves simétricas.

Blockchain

Os dados na blockchain são marcados com uma hash em cada bloco. Se o bloco for alterado, ou seja, (isso é apenas um EXEMPLOOO, antes que algum purista de blockchain me crucifique 😁) se alguém tentar mudar quantos bitcoins possuía ou quanto deveria enviar, a hash se alteraria e todos poderiam detectar que alguma coisa está errada.

A hash do bloco anterior é usada para calcular a hash do bloco atual, criando um link entre os blocos. (prestenção, vou falar sobre isso no futuro!)

Algoritmos

São exemplos de algoritmos, com link das suas implementações hash:

obs: coloquei como inseguro porque foram realizadas colisões em condições de laboratório, com arquivos pdfs e não senha plain text, mas ainda assim se seu sistema usa algumas dessas hash não se preocupe muito, pelo menos por enquanto.(mas se alguem perguntar, não fui eu quem disse)

Guardar suas senhas no banco usando hash é uma boa prática (na verdade, OBRIGATÓRIA) já que, se por algum motivo, o seu banco de dados for exposto por algum cracker, ele não consiga ter acesso direto à senha dos seus clientes.

Se voce ainda salva a senha no banco sem hash 😨

Então, é só criar a hash pra minha senha e problema resolvido?

Não totalmente.

Como as hash são quebradas

Pelas propriedades de hash vimos que dado uma hash H(x) não deveria ser computacionalmente viável descobrir a entrada x, logo não é possível “descriptografar” a hash, afinal, ela é uma operação que não permite retorno.

Mas, se sabemos que determinada entrada sempre gera a mesma hash, com toda certeza os crackers também sabem disso e existem alguns ataques já conhecidos para hash de senhas, que são:

Ataques de dicionários e força bruta

O jeito mais simples de tentar achar uma hash.

Um ataque de dicionário usa um arquivo contendo palavras, frases, senhas mais comuns e coisas assim para calcular a hash de cada uma e verificar se bate com alguma da lista ou do banco de dados.

Já o ataque de força bruta testa todas as possibilidades de palavras com um número definido de caracteres e verifica se o cálculo da hash é igual a algum da lista.

Tabela de consulta

Tabela de consulta é um método extremamente efetivo para quebrar várias hashes rapidamente. A ideia geral é pré-computar as hashes de senhas de um dicionário e guardar esse valor com sua senha correspondente. Uma boa implementação de uma tabela de consulta pode processar centenas de hashes por segundo, e conter bilhões de hashes.

Tabela de consulta reversa (tradução própria, hein? 😂)

Do inglês, Reverse Lookup Tables, esse ataque permite ao atacante aplicar um dicionário ou um ataque de força bruta para várias hashes ao mesmo tempo.

Primeiramente, o atacante cria uma tabela de consulta que mapeia cada hash de senha dos contatos da base de dados para uma lista de usuários que tem aquela hash.

O atacante então faz um ataque de dicionário ou força bruta, e ao encontrar uma senha que ele já possui, consegue uma lista de usuários que usam essa mesma senha. Esse ataque é bastante efetivo porque é comum muitos usuários terem a mesma senha em sistemas diferentes. (não usem a mesma senha em vários lugares, fazfavor)

Rainbow tables

Uma rainbow table é uma tabela de consulta de hashes pré calculados. É um exemplo prático do trade-off Memória e Tempo.

Ele se parece com a tabela de consulta, exceto que sacrifica a velocidade de quebrar a hash para fazer as tabelas menores. Por serem menores, as soluções para mais hashes podem ser armazenadas com o mesmo espaço, sendo mais efetivo em termos de armazenamento.

Temos exemplo? Temos exemplo!

Vamos exemplificar como seria um ataque simples:

Sexta fire, 18h, você está cansado da semana, e o seu chefe pede para subir uma funcionalidade de um bug extremamente simples, que salvava no log uma string errada, sem impacto no negócio, você pensando no que assistir no Netflix com a morena (ou fazer um pedidão no aiq e comer tudo sozinho, não posso julgar), sobe sem querer uma funcionalidade que o estagiário implementou usando uma lib que ele viu no youtube e tinha uma falha de segurança. Pronto, durante o final de semana um cracker invade o fucking banco de dados e consegue acesso à sua tabela de clientes. #sextou 😊

No banco, tem um cliente que, por exemplo, possui a senha 123456, armazenada usando o algoritmo SHA-1, que resultou na hash 7c4a8d09ca3762af61e59520943dc26494f8941b

Em posse desse hash, basta o cracker usar um site ou programa especifico, que testa se essa hash já foi computada anteriormente e, se sim, retorna o valor que gerou ela. Um exemplo desses sites é o http://md5decrypt.net/en/Sha1/#answer

facin, facin

A lista das 100 senhas mais usadas no mundo provavelmente já está em todos os bancos possíveis de hash, por isso, existem campanhas para usar senhas cada vez mais difíceis, um esforço para dificultar esses bancos de hashes. Mesmo com esses problemas, os usuários não querem ter a preocupação de usar senhas mais complexas e diferentes entre si. Então, como desenvolvedor, sabendo desses ataques, qual é a melhor solução pra esse problema? Da mesma forma que fazemos quando a comida tá sem gosto: colocamos mais “sal”, ou seja, usamos salt. (Pegou a referência? sal, comida, fome, aiqfome?)

Salt e implementações de segurança

ajuda noix

O conceito de salt é colocar alguma informação adicional na senha para adicionar complexidade ao calcular a hash, por exemplo:

Ao adicionar mais informações na senha, deixamos mais difícil para um cracker criar uma tabela de consulta. Mas como nem tudo é mil maravilhas, ainda precisamos de alguns cuidados com o salt.

O que evitar ao utilizar salt:

Reusar o salt

Mesmo sendo um salt hardcoded no programa ou gerado uma vez randomicamente. Usar um salt fixo é inefetivo por que se dois usuários possuem a mesma senha, eles obrigatoriamente terão a mesma hash. Dessa forma, um ataque de força bruta poderia ser aplicado simplesmente testando as possibilidades de salt e, assim que encontrado, todas as suas senhas ficariam vulneráveis.

Usar salt muito curto

Se seu salt é muito curto, um atacante pode construir uma tabela de consulta para todos os possíveis salts. Por exemplo, se você usa três caracteres ASCII tem somente 857,375 salts possíveis e, em termos computacionais, isso não é muito. Se cada tabela de consulta contem somente 1MB das senhas mais comuns, calcular todas possibilidades de salt geraria 837GB. Já temos HD de 1TB custando uns 200 reais hoje, um investimento muito barato comparado ao estrago que faria ao sistema, né?

Usar username como salt

Embora devessem ser únicos no sistema, os usernames geralmente são usados por outras contas em outros serviços. Um atacante poderia fazer a relação user-senha e criar tabelas de consulta para quebrar hashs que tem username como salt.

E aí, man? O que eu faço então?

Pra ser impossível computacionalmente criar uma tabela de consulta para cada salt possível, o salt deve ser longo. Uma boa regra é usar um salt que tem o mesmo tamanho da saída da função hash, a saída do SHA-256 é 256 bits (32 bytes), então o salt deveria ter ao menos 32 números randômicos.

Perceba que o salt não precisa ser segredo, ele só deve ajudar a evitar os ataques que listei. Eu, particularmente, gosto de usar o hash do timestamp da hora que a senha foi alterada(ou não 😈). Desse jeito, todos os requisitos são preenchidos e ainda deixo a data em um formato que defino (americano, brasileiro, ou até personalizado 👀), tornando ainda mais difícil a tarefa de gerar uma tabela de consulta.

Foi malz pelo textão, mas vou usar alguns conceitos do que eu falei hoje em posts futuros, então é bom sempre deixar esse post salvo para referências futuras (dica de ouro, morô?)

SUGESTÕES, RECLAMAÇÕES, DÚVIDAS E ELOGIOS

Por favor falar com o Clodoaldo. Ele tá sempre no twitter, instagram, face, e até no linkedin do aiq.

Mentira, o Crô é mó ocupado, pode me responder por aqui mesmo ou conversar comigo no Linkedin!

Isso é tudo pessoal, até a próxima 😉

--

--