Senhas: hashing com bcrypt, argon2 e pbkdf2
1. Por que hashing de senhas é essencial?
Armazenar senhas em texto puro é um dos erros mais graves que um desenvolvedor pode cometer. Em caso de vazamento do banco de dados, todas as senhas ficam expostas, comprometendo não apenas o sistema atual, mas também outros serviços onde o usuário reutiliza a mesma senha.
A diferença entre hash e criptografia é fundamental: criptografia é bidirecional (pode ser descriptografada com uma chave), enquanto hash é unidirecional — uma vez gerado, não é possível reverter ao valor original. Para senhas, hash é a escolha correta, pois nunca precisamos recuperar a senha original, apenas verificá-la.
Propriedades desejadas em um hash de senha:
- Unidirecionalidade: impossibilidade de reverter o hash para a senha original
- Resistência a colisão: dois valores diferentes não devem gerar o mesmo hash
- Lentidão computacional: dificultar ataques de força bruta tornando cada tentativa custosa
2. Conceitos fundamentais de hashing seguro
Salt é um valor aleatório único gerado para cada senha e armazenado junto com o hash. Impede que dois usuários com a mesma senha tenham hashes iguais e torna inviável o uso de rainbow tables. Deve ser gerado com um gerador criptograficamente seguro (ex: os.urandom() em Python, crypto/rand em Go).
Pepper é um valor secreto global (não armazenado no banco de dados, mas sim em uma variável de ambiente ou cofre de segredos). Adiciona uma camada extra: mesmo que o banco vaze, o atacante não tem o pepper para calcular hashes. A diferença crucial: salt é público (armazenado com o hash), pepper é secreto.
Custo computacional (work factor) define quantas iterações ou memória o algoritmo consumirá. Deve ser ajustado periodicamente conforme o hardware evolui. Quanto maior o custo, mais lento o ataque de força bruta.
3. bcrypt: o padrão consolidado
bcrypt foi projetado em 1999 por Niels Provos e David Mazières, baseado no cifrador Blowfish. É um algoritmo adaptativo: seu custo pode ser aumentado ao longo do tempo.
Funcionamento interno: bcrypt usa uma variação do Blowfish chamada "eksblowfish" (expensive key schedule Blowfish), que executa múltiplas rodadas de expansão de chave para tornar o hashing intencionalmente lento.
Parâmetro de configuração: o cost factor (potência de 2 para o número de rodadas). Exemplo: cost=12 significa 2^12 = 4096 iterações. O valor recomendado atualmente é entre 10 e 14, dependendo da capacidade do servidor.
// Exemplo de hash com bcrypt (cost=12)
hash = bcrypt.hashpw(senha, bcrypt.gensalt(rounds=12))
// Exemplo de verificação
bcrypt.checkpw(senha, hash_armazenado) // retorna True/False
Limitações: o tamanho máximo da senha é 72 bytes. Senhas maiores são truncadas silenciosamente, o que pode ser um problema de segurança. Além disso, bcrypt não é resistente a ataques com GPU/ASIC da mesma forma que Argon2.
4. PBKDF2: o legado amplamente suportado
PBKDF2 (Password-Based Key Derivation Function 2) faz parte do padrão PKCS#5 e é amplamente suportado em praticamente todas as linguagens e frameworks. Funciona aplicando repetidamente uma função HMAC (ex: HMAC-SHA256) milhares de vezes.
Configuração: define-se o número de iterações e o algoritmo hash subjacente. O NIST recomenda atualmente no mínimo 600.000 iterações para SHA-256 (recomendação de 2023).
// Exemplo com PBKDF2-HMAC-SHA256 (600.000 iterações)
hash = pbkdf2_hmac('sha256', senha, salt, 600000)
// Verificação: repete o processo e compara hashes
hash_verificacao = pbkdf2_hmac('sha256', senha_tentativa, salt_armazenado, 600000)
if hash_verificacao == hash_armazenado:
// senha correta
Quando usar PBKDF2: ideal para cenários de compatibilidade com sistemas legados, especialmente quando a biblioteca disponível não suporta bcrypt ou Argon2. Desvantagens: é mais vulnerável a ataques com hardware especializado (GPU, FPGA, ASIC) por não exigir memória significativa, permitindo paralelização massiva.
5. Argon2: o vencedor da competição PHC
Argon2 venceu a Password Hashing Competition (PHC) em 2015 e é o algoritmo mais moderno e seguro disponível. Possui três variantes:
- Argon2d: resistente a ataques de side-channel via GPU, usa acesso à memória dependente dos dados
- Argon2i: resistente a ataques de side-channel via timing, usa acesso à memória independente dos dados
- Argon2id (recomendado): combina as abordagens de Argon2i e Argon2d, oferecendo a melhor proteção geral
Parâmetros críticos:
- t (time): número de iterações
- m (memory): uso de memória em KiB
- p (parallelism): grau de paralelismo (threads)
// Exemplo com Argon2id (parâmetros recomendados para 2024)
hash = argon2.hash_password(senha,
time_cost=3, // t = 3 iterações
memory_cost=65536, // m = 64 MiB
parallelism=4, // p = 4 threads
hash_len=32, // tamanho do hash em bytes
salt_len=16) // tamanho do salt em bytes
// Verificação
argon2.verify_password(hash_armazenado, senha_tentativa) // retorna True/False
Vantagens sobre bcrypt e PBKDF2: Argon2 exige uma quantidade configurável de memória, tornando ataques com GPU e ASIC extremamente caros (um ASIC precisa de chips de memória caros, não apenas lógica). Além disso, não tem limite de tamanho de senha.
6. Comparação prática entre os algoritmos
| Característica | bcrypt | PBKDF2 | Argon2id |
|---|---|---|---|
| Resistência a GPU | Média | Baixa | Alta |
| Resistência a ASIC | Média | Baixa | Alta |
| Uso de memória | Baixo | Baixo | Alto (configurável) |
| Tamanho máximo senha | 72 bytes | Ilimitado | Ilimitado |
| Maturidade | Alta (desde 1999) | Muito alta (desde 2000) | Média (desde 2015) |
| Suporte em linguagens | Muito alto | Muito alto | Crescente |
Recomendações por cenário:
- Sistemas legados: PBKDF2 é seguro se configurado com iterações adequadas (600k+)
- Novos projetos: Argon2id é a escolha ideal
- Ambientes com restrição severa de memória: bcrypt com cost factor >= 12
- Sistemas críticos (bancos, saúde): Argon2id com parâmetros elevados (m=128MiB, t=4, p=4)
Para escolher o work factor ideal, meça o tempo de hashing no servidor alvo. O hash deve levar entre 250ms e 500ms — tempo suficiente para dificultar ataques, mas não a ponto de prejudicar a experiência do usuário no login.
7. Implementação segura e boas práticas
Exemplo completo com bcrypt (Python):
import bcrypt
# Hashing
senha = b"minha_senha_segura_123"
salt = bcrypt.gensalt(rounds=12)
hash_armazenado = bcrypt.hashpw(senha, salt)
# Verificação (ex: durante login)
senha_tentativa = b"minha_senha_segura_123"
if bcrypt.checkpw(senha_tentativa, hash_armazenado):
print("Senha correta!")
else:
print("Senha incorreta.")
Exemplo completo com Argon2id (Python):
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16
)
# Hashing
hash_armazenado = ph.hash("minha_senha_segura_123")
# Verificação
try:
ph.verify(hash_armazenado, "minha_senha_segura_123")
print("Senha correta!")
except:
print("Senha incorreta.")
Erros comuns a evitar:
1. Salt fixo ou previsível — use gerador criptograficamente seguro
2. Work factor muito baixo — teste periodicamente e aumente conforme hardware evolui
3. Ignorar atualização periódica — re-hash senhas no próximo login quando aumentar o custo
4. Truncar senhas sem aviso — especialmente com bcrypt (limite de 72 bytes)
5. Armazenar pepper no banco de dados — use variáveis de ambiente ou cofre de segredos
Lembre-se: a segurança de senhas é uma corrida armamentista. O que é seguro hoje pode não ser amanhã. Mantenha-se atualizado, monitore as recomendações do OWASP e do NIST, e ajuste seus parâmetros regularmente.
Referências
- OWASP Password Storage Cheat Sheet — Guia oficial do OWASP com recomendações atualizadas sobre armazenamento seguro de senhas, incluindo bcrypt, PBKDF2 e Argon2
- Argon2: The PHC Winner (RFC 9106) — Especificação oficial do Argon2, documentação completa dos parâmetros e variantes
- bcrypt: Documentação oficial (OpenBSD) — Página de manual do bcrypt no OpenBSD, incluindo detalhes sobre o algoritmo e parâmetros
- NIST SP 800-63B: Digital Identity Guidelines — Diretrizes do NIST sobre autenticação, incluindo recomendações de hashing de senhas e iterações mínimas para PBKDF2
- Password Hashing Competition (PHC) — Site oficial da competição que elegeu o Argon2 como vencedor, com artigos técnicos e comparações entre algoritmos
- Argon2: Documentação da biblioteca Python — Documentação oficial da implementação Argon2 em Python, com exemplos práticos de hashing e verificação
- PBKDF2: RFC 8018 (PKCS#5 v2.1) — Especificação oficial do PBKDF2, incluindo detalhes sobre iterações e algoritmos HMAC suportados