Hashing de senhas: por que MD5 e SHA1 estão mortos (use Argon2)

1. A Ilusão da Segurança: Por que MD5 e SHA1 são insuficientes

Durante anos, MD5 e SHA1 foram considerados padrões aceitáveis para proteger senhas. Hoje, sabemos que essa confiança era uma ilusão perigosa. O principal problema é a velocidade: esses algoritmos foram projetados para serem rápidos em hardware comum, o que os torna perfeitos para verificar integridade de arquivos, mas terríveis para hashing de senhas.

Um atacante com uma GPU moderna pode calcular bilhões de hashes MD5 por segundo. Em 2017, a equipe do Google demonstrou uma colisão prática para SHA1, provando que dois arquivos diferentes podem produzir o mesmo hash. Para MD5, colisões são possíveis desde 2004. Além disso, nenhum desses algoritmos inclui salt internamente — você precisa implementar manualmente, e muitos sistemas simplesmente ignoravam essa etapa.

# Exemplo de vulnerabilidade: MD5 sem salt
Hash MD5 de "senha123": 482c811da5d5b4bc6d497ffa98491e38
Hash MD5 de "senha456": e10adc3949ba59abbe56e057f20f883e
# Com uma rainbow table pré-computada, ambos são revertidos em segundos

2. O Erro Histórico: Confundir Hash com Hashing de Senhas

O erro mais comum entre desenvolvedores iniciantes é tratar funções hash criptográficas (como MD5, SHA1, SHA256) como equivalentes a funções de derivação de chave (KDFs). A diferença é fundamental: hashes criptográficos são rápidos e determinísticos, enquanto KDFs são propositalmente lentos e custosos em recursos.

Quando você usa hash("senha") diretamente, expõe seus usuários a ataques de rainbow tables — tabelas pré-computadas que mapeiam hashes comuns de volta para senhas. Em 2012, o vazamento do LinkedIn expôs 6,5 milhões de hashes SHA1 sem salt; em 24 horas, 90% já estavam quebrados. Casos como Ashley Madison (MD5) e Adobe (3DES reversível) mostram como escolhas erradas de algoritmo expõem milhões de pessoas.

# Comparação de tempo para calcular hash de uma senha
MD5:    0.0000003 segundos  (3 milhões/s)
SHA1:   0.0000004 segundos  (2.5 milhões/s)
SHA256: 0.0000008 segundos  (1.2 milhões/s)
Argon2: 0.0500000 segundos  (20/s) ← propositalmente lento

3. A Evolução dos Algoritmos: Do bcrypt ao Argon2

A transição começou com o bcrypt (1999), que introduziu o conceito de custo computacional ajustável — um parâmetro de "trabalho" que força o algoritmo a executar múltiplas rodadas. Em 2009, o scrypt adicionou dependência de memória, tornando ataques com hardware especializado (ASICs) mais caros.

O marco definitivo veio em 2015 com a Password Hashing Competition (PHC), que elegeu o Argon2 como vencedor. Desenvolvido por Alex Biryukov, Daniel Dinu e Dmitry Khovratovich, o Argon2 combina resistência a ataques de GPU, ASIC e side-channel em um único algoritmo.

# Evolução dos parâmetros de segurança
bcrypt (cost=10):   ~100ms por hash
scrypt (N=2^14):    ~200ms por hash
Argon2id (t=3, m=64MB): ~300ms por hash

4. Argon2 em Detalhes: Parâmetros que Salvam Senhas

O Argon2 possui três variantes principais:

  • Argon2d: otimizado para resistência a ataques de GPU/ASIC (usa acesso à memória dependente de dados)
  • Argon2i: otimizado para resistência a ataques side-channel (acesso independente de dados)
  • Argon2id: híbrido (recomendado para senhas) — começa como Argon2i e transita para Argon2d

Os três parâmetros críticos são:

  1. Tempo (t): número de iterações — aumenta o custo computacional
  2. Memória (m): quantidade de memória em KiB — dificulta paralelização
  3. Paralelismo (p): número de threads — deve ser 1 ou 2 para hashing de senhas

Para hardware moderno (2024), recomenda-se:

# Configuração recomendada para Argon2id
t = 3      # 3 iterações
m = 65536  # 64 MB de memória
p = 1      # 1 thread (evita paralelismo excessivo)
salt = 16 bytes aleatórios
hash_length = 32 bytes

5. Implementação Prática: Argon2 no Mundo Real

Python com argon2-cffi

# Instalação: pip install argon2-cffi
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,
    memory_cost=65536,
    parallelism=1,
    hash_len=32,
    salt_len=16
)

# Criando hash
hash = ph.hash("minha_senha_segura_123")
print(hash)
# Exemplo: $argon2id$v=19$m=65536,t=3,p=1$c29tZXNhbHQ$hashcodificado

# Verificando senha
try:
    ph.verify(hash, "senha_errada")
except:
    print("Senha incorreta!")

# Atualizando parâmetros (re-hash automático)
if ph.check_needs_rehash(hash):
    novo_hash = ph.hash("minha_senha_segura_123")

Node.js com argon2

// Instalação: npm install argon2
const argon2 = require('argon2');

async function exemplo() {
    try {
        // Criando hash
        const hash = await argon2.hash('minha_senha_segura_123', {
            type: argon2.argon2id,
            memoryCost: 65536,   // 64 MB
            timeCost: 3,
            parallelism: 1
        });
        console.log(hash);
        // $argon2id$v=19$m=65536,t=3,p=1$...

        // Verificando
        const valido = await argon2.verify(hash, 'minha_senha_segura_123');
        console.log('Senha correta:', valido); // true
    } catch (err) {
        console.error('Erro no hashing:', err);
    }
}

6. Armadilhas Comuns e Boas Práticas

Nunca armazene senhas em texto plano ou com criptografia reversível (AES, RSA). O hash deve ser unidirecional — nem você deve conseguir "descriptografar".

Salt único por senha: gere 16 bytes aleatórios para cada senha e armazene junto com o hash. O Argon2 já faz isso automaticamente na maioria das implementações.

Atualização de hashes legados: durante o login, verifique se o hash atual usa parâmetros obsoletos. Se sim, recalcule com Argon2 e atualize o banco:

# Estratégia de upgrade durante login
1. Usuário envia senha
2. Verifique com algoritmo antigo (ex: MD5+salt)
3. Se válido, calcule novo hash com Argon2
4. Armazene novo hash, remova o antigo
5. Use Argon2 da próxima vez

7. O Futuro do Hashing de Senhas

A computação quântica representa uma ameaça futura, mas Argon2 já oferece resistência parcial por seu uso intensivo de memória. A tendência é integrar hashing forte com autenticação multifator (MFA) e WebAuthn (chaves de segurança físicas).

Para sistemas novos, considere passkeys (chaves criptográficas armazenadas no dispositivo do usuário) como alternativa a senhas tradicionais. Enquanto senhas existirem, Argon2id com parâmetros adequados continuará sendo o padrão-ouro.

# Comparação final de segurança
MD5:    ❌ Quebrado (colisões, velocidade)
SHA1:   ❌ Quebrado (colisões, velocidade)
SHA256: ⚠️ Apenas com salt e múltiplas iterações
bcrypt: ✅ Bom, mas limitado em memória
scrypt: ✅ Melhor, mas menos testado que Argon2
Argon2: ✅✅ Recomendado (PHC winner, resistente a GPU/ASIC)

Referências