Antes de guardar ou usar uma senha, ela precisa ser hasheada. E não de qualquer jeito, com qualquer algoritmo meia-boca. Nesta segunda parte você vai ver como aplicar um hash adequado nas senhas e por que cada escolha importa: desde salt e parâmetros de performance até por que algoritmos rápidos (tipo MD5/SHA puro) são um convite ao desastre.
Pronto para ver o que realmente funciona (e o que é só teatro de segurança)? Vou trazer os detalhes práticos e exemplos de parâmetros para você sair daqui com uma implementação minimamente decente, e sem desculpas.
O propósito do hash
Na essência, um hash é uma função que pega um texto de entrada e o transforma em outro texto de tamanho fixo e truncado, o famoso hash (ou digest, se você quiser soar mais chique).
A ideia parece simples, mas é justamente essa simplicidade que engana. Muita gente acha que “é só jogar no MD5” e pronto, problema resolvido. Não é bem assim. O propósito do hash não é só transformar dados em uma saída “bonita e curta”, mas garantir propriedades como determinismo, resistência a colisões e irreversibilidade.
Sem isso, seu hash vira só enfeite, e atacante adora enfeite mal feito.
Hashes são algoritmos one-way: não dá para reverter o resultado e voltar ao texto original. Além disso, são determinísticos, a mesma entrada sempre gera a mesma saída.
Na primeira parte mostrei por que aplicar hash em senhas é uma boa prática: o digest armazenado funciona como uma segunda linha de defesa. Se o repositório for comprometido, o atacante não vai encontrar as senhas em texto puro, mas sim valores que não podem ser revertidos diretamente. Isso eleva bastante o custo do ataque.
E se você ainda não leu a Parte 1, faz o favor para mim e para você: Segurança – Parte 1 – Como armazenar senhas. (Afinal, pular a primeira parte e cair direto aqui é quase tão perigoso quanto usar MD5 em 2025.)
Por que não MD5, SHA-* (SHA1, SHA2, SHA3)?
MD5, SHA-1, SHA-2, SHA-3 e afins são general-purpose hash functions: foram projetadas para produzir um digest de grandes quantidades de dados o mais rápido possível. Ótimas para verificar integridade de arquivos, péssimas para armazenar senhas. Por quê? Porque rápidas é exatamente o que um atacante quer: capacidade de testar milhões (ou bilhões) de senhas por segundo durante um ataque de brute force ou dictionary.
Brute-force attack
Com hardware modesto e ferramentas como hashcat, é surpreendentemente barato testar uma quantidade grande de senhas. Um computador de mesa com GPUs decentes quebra senhas curtas muito rápido, por exemplo, senhas de 6 caracteres (incluindo lowercase, UPPERCASE, digits e symbols) podem ser testadas em segundos. Se alguém monta um rig maior, o throughput salta para centenas de milhões ou bilhões de hashes por segundo. Resultado prático: um algoritmo rápido como SHA-1 ou MD5 torna trivial a tentativa via brute force contra senhas de complexidade moderada.
Dictionary attack
Brute force pode ser refinado com um dictionary attack: em vez de testar todo o espaço, o atacante testa uma lista inteligente, senhas comuns, palavras de dicionários, coleções extraídas de vazamentos anteriores. Isso reduz enormemente o tempo para encontrar senhas reais.
"Um SALT não vai te proteger"
Não, um salt sozinho não impede brute-force nem dictionary attacks. Se salt e hash estão disponíveis no dump do banco (como é habitual), adicionar um salt único por usuário evita rainbow tables, mas não reduz a velocidade com que um invasor pode testar candidatos: ele só precisa recomputar hash(salt + candidate) para cada tentativa. Ou seja, salt não é mágica, é necessário, mas insuficiente.
Salt é besteira?
De forma alguma. Salt é essencial, apenas não é a solução completa. O papel do salt é:
- impedir precomputed attacks (rainbow tables), forçando o atacante a recomputar por usuário;
- garantir que a mesma senha em dois usuários gere hashes diferentes, quebrando paralelismo de ataques sobre hashes idênticos.
Mas para realmente dificultar ataques modernos você precisa, além do salt, usar uma password hashing function projetada para ser slow e memory-hard (por exemplo: Argon2id, bcrypt, scrypt).
O algoritmo deve ser lento
Ao contrário das general-purpose hash functions (tipo MD5/SHA), que foram projetadas para ser rápidas e calcular digests de grandes volumes de dados, os password hashing algorithms foram feitos para ser lentos por design.
Lembre-se: o hash é a segunda linha de defesa. O invasor já passou pela primeira (acesso ao repositório). Para ultrapassar essa segunda linha, ele precisa investir tempo e poder de computação. Portanto, o objetivo é exatamente esse: tornar o custo do ataque tão alto que vire projeto de infraestrutura caro e demorado.
Password hashes devem consumir recursos (CPU, memória, tempo) suficientes para tornar ataques de brute-force e dictionary mais lentos e economicamente inviáveis.
Argon2, bcrypt e scrypt, e PBKDF2
Abaixo um resumo prático sobre esses algoritmos de password hashing (com termos em inglês para não correr do tecnicês).
bcrypt
bcrypt deriva do algoritmo Blowfish e introduziu o conceito de work factor (cost). Ele usa salt por usuário e é considerado CPU-hardened, ou seja, força o uso de CPU para calcular cada hash. A configuração principal é o cost/work factor: aumentar esse valor aumenta exponencialmente o esforço necessário para calcular cada hash. À medida que hardware fica mais rápido, basta aumentar o cost para manter o ataque caro. Simples e eficaz, mas não é memory-hard, então ataques massivamente paralelos em hardware especializado ainda conseguem vantagens.
scrypt
scrypt foi projetado para ser memory-hard além de CPU-intensive. Isso significa que, além de exigir CPU, ele também consome muita memory durante a computação do hash. Memory-hardness reduz muito a eficiência de ataques em GPUs e ASICs, porque essas arquiteturas costumam ter pouca memória por núcleo. É uma boa escolha quando você quer forçar custos em ambos os eixos: tempo e memória.
Argon2
Argon2 adiciona três dimensões configuráveis: time cost, memory e parallelism (threads). A variante Argon2id é a recomendada hoje como um bom compromisso entre resistência a ataques por GPU/ASIC e resistência a ataques paralelos. Com Argon2 você faz o atacante precisar não só de muita CPU e memória, mas também de múltiplos núcleos físicos, o que eleva ainda mais o custo do ataque. É a opção moderna e flexível para password hashing.
PBKDF2
PBKDF2 (usado, por exemplo, em alguns stacks como ASP.NET Identity por padrão) aplica um HMAC iterado várias vezes, o que a torna configurável via número de iterações. Implementações comuns usam HMAC-SHA256, salt de 128-bit e subkey de 256-bit, e configurações como 10.000 iterações foram padrão por anos. PBKDF2 é aceitável quando bem parametrizado, mas não é memory-hard; por isso, hoje em dia muitos recomendam preferir Argon2 (ou scrypt/bcrypt quando Argon2 não for possível). Se você usa PBKDF2, aumente significativamente o número de iterações conforme seu ambiente permitir.
Qual algoritmo de hash usar?
Argon2id. Entre especialistas é quase consenso, e com razão. Argon2 foi o vencedor da Password Hashing Competition (julho/2015) e trouxe, de cara, um design pensado para as ameaças atuais (GPU/ASIC, ataques paralelos, etc). Existem três variantes com objetivos diferentes: Argon2d, Argon2i e Argon2id, cada uma com seu foco.
Resumo prático das diferenças:
- Argon2d: acesso à memória data-dependent, bom para proof-of-work e aplicações que não se preocupam com ataques side-channels.
- Argon2i: acesso à memória data-independent, preferido para password hashing e key derivation quando side-channels são uma preocupação.
- Argon2id: mistura os dois, age como Argon2i na primeira metade da primeira iteração e como Argon2d no restante, oferecendo proteção contra side-channels e ao mesmo tempo boas propriedades contra brute-force por time-memory tradeoffs.
Na prática, a recomendação é simples: se você não souber qual escolher, use Argon2id, é o balanço mais seguro entre proteção contra side-channels e custo para ataques. OWASP recomenda Argon2, e o IETF (draft do CFRG) diz claramente que, caso haja dúvida ou ameaça de side-channels, escolha Argon2id.
(Em outras palavras: pare de procurar desculpas. Se seu ambiente permite, use Argon2id. Se por algum motivo não for possível, prefira bcrypt ou scrypt, e só então recorra a PBKDF2 quando houver necessidade de compatibilidade ou requisitos FIPS.)
Playground
Quer saber como é a hash do bcrypt, scrypt e argon2? Acesse essa ferramenta online: