Ao contrário do que as respostas mais votadas aqui enfatizam, a não injetividade (ou seja, que existem várias strings hashing para o mesmo valor) de uma função hash criptográfica causada pela diferença entre o tamanho de entrada grande (potencialmente infinito) e o tamanho de saída fixo não é o ponto importante - na verdade, preferimos funções hash onde essas colisões acontecem tão raramente quanto possível.
Considere esta função (em notação PHP, como a pergunta):
function simple_hash($input) {
return bin2hex(substr(str_pad($input, 16), 0, 16));
}
Isso acrescenta alguns espaços, se a string for muito curta, e então pega os primeiros 16 bytes da string e a codifica como hexadecimal. Ele tem o mesmo tamanho de saída de um hash MD5 (32 caracteres hexadecimais ou 16 bytes se omitirmos a parte bin2hex).
print simple_hash("stackoverflow.com");
Isso resultará em:
737461636b6f766572666c6f772e636f6d
Essa função também tem a mesma propriedade de não injetividade destacada pela resposta de Cody para MD5: Podemos passar strings de qualquer tamanho (desde que caibam em nosso computador), e ela produzirá apenas 32 dígitos hexadecimais. Claro que não pode ser injetivo.
Mas, neste caso, é trivial encontrar uma string que mapeie para o mesmo hash (apenas aplique hex2bin
em seu hash e você terá). Se sua string original tinha o comprimento 16 (como nosso exemplo), você ainda obterá esta string original. Nada desse tipo deve ser possível para MD5, mesmo se você souber que o comprimento da entrada foi bastante curto (exceto por tentar todas as entradas possíveis até encontrarmos uma que corresponda, por exemplo, um ataque de força bruta).
As suposições importantes para uma função hash criptográfica são:
- é difícil encontrar qualquer string produzindo um determinado hash (resistência de pré-imagem)
- é difícil encontrar qualquer string diferente produzindo o mesmo hash de uma determinada string (resistência de segunda pré-imagem)
- é difícil encontrar qualquer par de cordas com o mesmo hash (resistência à colisão)
Obviamente, minha simple_hash
função não cumpre nenhuma dessas condições. (Na verdade, se restringirmos o espaço de entrada a "strings de 16 bytes", minha função se torna injetiva e, portanto, é até resistente à segunda pré-imagem e à colisão.)
Agora existem ataques de colisão contra MD5 (por exemplo, é possível produzir um par de strings, mesmo com um determinado prefixo, que têm o mesmo hash, com bastante trabalho, mas não impossível muito trabalho), então você não deve usar MD5 para qualquer coisa crítica. Ainda não há um ataque de pré-imagem, mas os ataques ficarão melhores.
Para responder à pergunta real:
O que há com relação a essas funções que torna impossível reconstituir as strings resultantes?
O que o MD5 (e outras funções hash construídas na construção Merkle-Damgard) fazem efetivamente é aplicar um algoritmo de criptografia com a mensagem como a chave e algum valor fixo como o "texto simples", usando o texto cifrado resultante como o hash. (Antes disso, a entrada é preenchida e dividida em blocos, cada um desses blocos é usado para criptografar a saída do bloco anterior, XORed com sua entrada para evitar cálculos reversos.)
Os algoritmos de criptografia modernos (incluindo aqueles usados em funções hash) são feitos de forma a dificultar a recuperação da chave, mesmo com texto simples e texto cifrado (ou mesmo quando o adversário escolhe um deles). Eles geralmente fazem isso realizando várias operações de embaralhamento de bits de forma que cada bit de saída seja determinado por cada bit chave (várias vezes) e também por cada bit de entrada. Dessa forma, você só pode reconstituir facilmente o que acontece internamente se souber a chave completa e a entrada ou saída.
Para funções hash do tipo MD5 e um ataque de pré-imagem (com uma string com hash de bloco único, para facilitar as coisas), você só tem entrada e saída de sua função de criptografia, mas não a chave (é isso que você está procurando).