Hashing de senhas: bcrypt, scrypt e Argon2
Por que nunca guardar senha em texto nem em hash rapido, e como bcrypt, scrypt e Argon2id protegem com salt, work factor e custo de memoria de verdade.
Vazamentos de banco de dados acontecem o tempo todo. A pergunta não é se a sua tabela de usuários um dia vai parar num fórum, e sim o que o atacante encontra quando ela parar. Se você guardou senha em texto puro, o jogo acabou no instante do dump. Se guardou com MD5 ou SHA-256, o jogo acaba poucas horas depois, porque esses algoritmos foram projetados para serem rápidos — e velocidade é exatamente a propriedade que você não quer ao proteger senha. Este artigo explica por que armazenar senha exige um hash deliberadamente lento, o que separa um hash de senha decente de um hash genérico, e como bcrypt, scrypt e Argon2 resolvem o problema.
Por que hash rápido é o inimigo
Existe uma confusão comum entre "hash criptográfico" e "hash de senha". MD5, SHA-1, SHA-256 são funções de hash de propósito geral: úteis para checksum de arquivo, assinatura, integridade. A meta delas é processar gigabytes por segundo. Esse é o problema. Uma GPU moderna calcula bilhões de SHA-256 por segundo. Se o seu banco vazou e as senhas estavam em SHA-256, o atacante simplesmente roda um dicionário de senhas comuns mais variações e bate cada palpite contra o hash. Senha de oito caracteres minúsculos cai em minutos.
Existe também a rainbow table: tabelas pré-computadas que mapeiam hash de volta para a senha original. Contra SHA-256 puro, sem nada a mais, elas funcionam, porque o mesmo input sempre gera o mesmo output. Hashear "123456" em SHA-256 dá sempre o mesmo valor, em qualquer servidor do planeta.
A conclusão é direta: hash de senha não pode ser rápido, e não pode ser determinístico de forma ingênua. Ele precisa de três propriedades que MD5 e SHA não têm.
O que um hash de senha precisa ter
Primeiro, ser lento e ajustável. Você quer que calcular um único hash custe, digamos, 100 a 250 milissegundos. Para o seu login isso é imperceptível. Para um atacante tentando bilhões de palpites, vira uma parede. E "ajustável" importa porque hardware fica mais rápido todo ano — você precisa de um parâmetro, o work factor, que permita aumentar o custo com o tempo sem trocar de algoritmo.
Segundo, ter salt único por senha. O salt é um valor aleatório gerado para cada usuário e concatenado à senha antes do hash. Ele mata rainbow tables de uma vez: como cada senha tem um salt diferente, duas pessoas com a senha "123456" produzem hashes completamente distintos, e nenhuma tabela pré-computada serve. O salt não é segredo — ele fica guardado junto do hash, em texto.
Terceiro, idealmente ser memory-hard: forçar o cálculo a consumir uma quantidade significativa de memória RAM. Isso ataca diretamente o modelo econômico do adversário. GPUs e ASICs têm milhares de núcleos baratos, mas memória rápida é cara e limitada. Um algoritmo que exige, digamos, 64 MB por cálculo derruba o paralelismo massivo que torna o ataque em GPU viável.
bcrypt: o veterano sólido
O bcrypt existe desde 1999 e continua perfeitamente defensável. Ele é derivado da cifra Blowfish, tem work factor embutido (o "cost", normalmente entre 10 e 14, onde cada incremento dobra o trabalho) e gera o salt automaticamente. O resultado é uma string que carrega tudo dentro de si:
$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
Lendo da esquerda para a direita: $2b$ é a versão do algoritmo, 12 é o work factor, os 22 caracteres seguintes são o salt, e o resto é o hash. Salt e custo viajam junto do hash — você não guarda nada separado.
O bcrypt tem uma peculiaridade que vale conhecer: ele trunca a entrada em 72 bytes. Senhas (ou passphrases longas) acima disso têm o excedente ignorado silenciosamente, o que em casos extremos vira problema de segurança. Para a maioria das aplicações, 72 bytes é mais que suficiente, mas é bom saber. Calibrar o custo é prático: gerar e conferir um hash bcrypt com diferentes work factors ajuda a achar o valor que dá ~200 ms no seu servidor.
scrypt e Argon2: a geração memory-hard
O scrypt (2009) foi um dos primeiros a tratar memória como recurso de defesa. Além do custo de CPU, ele tem parâmetros de memória e paralelismo, então um atacante não consegue só jogar mais núcleos no problema — precisa de mais RAM por núcleo, o que encarece o ataque em GPU/ASIC. É sólido e amplamente disponível.
O Argon2 é o estado da arte. Venceu a Password Hashing Competition (PHC) em 2015, justamente a competição feita para escolher um sucessor à altura. Ele vem em três variantes: Argon2d (resistente a ataque de tempo-memória, mas vulnerável a side-channel), Argon2i (resistente a side-channel) e Argon2id, que combina as duas abordagens. Para senha de login, a recomendação atual e quase universal é Argon2id. Ele expõe três parâmetros independentes: custo de tempo (iterações), custo de memória (quanto de RAM) e paralelismo (quantas threads). Isso te dá controle fino sobre o trade-off entre segurança e a carga no seu servidor.
Como a verificação funciona na prática
Como o hash carrega salt e parâmetros dentro de si, você não precisa guardar nada separado nem reimplementar a lógica. No cadastro, você gera o hash e salva a string inteira. No login, você passa a senha digitada e o hash salvo para a função de verificação, que extrai o salt e o custo do próprio hash, recalcula e compara:
// cadastro
hash = argon2id_hash(senha) // salt gerado e embutido
salvar(usuario, hash)
// login
hash = buscar_hash(usuario)
if argon2id_verify(senha_digitada, hash):
// ok
Nunca compare hashes manualmente com ==; use a função de verificação da biblioteca, que faz comparação em tempo constante para evitar timing attacks.
Dois detalhes finais. O pepper é um segredo extra, igual para toda a aplicação, guardado fora do banco (numa variável de ambiente ou cofre) e misturado à senha antes do hash. Se só o banco vazar, o pepper continua protegendo — é uma camada opcional, mas barata. E o rehash: quando você aumentar o work factor (porque o hardware ficou mais rápido, ou porque migrou de bcrypt para Argon2id), faça isso de forma oportunista no próximo login bem-sucedido do usuário, quando você tem a senha em texto na mão por um instante. Detectar que um hash usa parâmetros antigos e regravá-lo é trivial.
Não confunda com criptografia reversível
Vale separar dois mundos. Cifra simétrica como AES é reversível por design: você criptografa para depois descriptografar com a chave. Isso serve para dados que você precisa recuperar — número de cartão, mensagem, arquivo. Senha não é assim. Você nunca precisa recuperar a senha original; só precisa confirmar que quem está logando sabe a mesma senha. Por isso hash é mão única e proposital: nem você consegue reverter. Se você quer entender o panorama maior de cifras e chaves, vale ler criptografia simétrica vs assimétrica, mas guarde a regra: senha se faz hash, dado recuperável se criptografa.
Perguntas frequentes
bcrypt ainda é seguro em 2026?
Sim, com a ressalva de usá-lo com um work factor adequado (12 ou mais, idealmente calibrado para ~200 ms no seu hardware) e de estar ciente do limite de 72 bytes. O bcrypt não é memory-hard, então em tese é mais barato de atacar em GPU do que o Argon2id, mas para a esmagadora maioria das aplicações ele continua perfeitamente aceitável e está em todo lugar — quase toda linguagem tem uma implementação madura.
Qual escolher para um projeto novo?
Argon2id. É a recomendação da PHC, da OWASP e da maioria dos guias atuais, porque ataca o custo de tempo e de memória ao mesmo tempo. Comece com parâmetros conservadores (por exemplo 19 MB de memória, 2 iterações, 1 thread) e ajuste medindo o tempo real no seu servidor. Se a sua stack só tiver bcrypt disponível e bem testado, bcrypt segue sendo uma escolha defensável.
Preciso guardar o salt em uma coluna separada?
Não. Tanto bcrypt quanto scrypt e Argon2 embutem o salt (e os parâmetros de custo) na própria string de saída. Você guarda uma única coluna de texto com o hash completo, e a função de verificação extrai tudo de que precisa de lá. Criar coluna separada de salt é um padrão antigo e desnecessário com esses algoritmos.
Devo usar pepper sempre?
Não é obrigatório, mas é barato e ajuda. O pepper protege especificamente o cenário em que só o banco vaza, e não o servidor de aplicação inteiro com suas variáveis de ambiente. A regra de ouro: salt fica com o hash, pepper fica fora do banco. Nunca invente um esquema próprio de "embaralhar" a senha além disso — você só vai introduzir fraqueza.
O que levar daqui
Senha nunca em texto, nunca em hash rápido. Use Argon2id em projeto novo; aceite bcrypt onde ele já estiver bem implantado. Confie no salt embutido, considere um pepper fora do banco, e refaça o hash quando subir o custo. E, acima de tudo, não invente seu próprio esquema de hashing — as bibliotecas maduras já resolveram os detalhes difíceis, e o seu trabalho é só escolher bons parâmetros e medir.
- 01 Nubank Croma: o que vem no plano, quanto custa e pra quem realmente compensa O Nubank lançou o Croma, um plano de média renda entre o cartão gratuito e o Ultravioleta. Veja o que está incluído, o cashback real, os R$ 39 de mensalidade (e como zerar) e faça a conta antes de aderir.
- 02 TypeScript vale a pena em projetos pequenos? TS adiciona um build step, mas paga em autocompletar, refactor seguro e bugs pegos cedo. Veja o tradeoff real e quando NAO compensa em projeto pequeno.