Quase toda explicação de hash começa com a analogia do liquidificador: entra fruta, sai vitamina, não dá pra voltar. É boa pra quem nunca ouviu falar do assunto e é inútil pra quem precisa decidir se usa hash ou assinatura num sistema real.
Vou pela outra ponta: o que a função te promete, e o que ela deixa por sua conta.
As três promessas
Uma função de hash criptográfico pega uma entrada de qualquer tamanho e devolve uma saída de tamanho fixo. SHA-256 devolve 256 bits, sempre, seja um byte ou um filme.
Ela promete três coisas.
Determinismo. A mesma entrada dá sempre a mesma saída. Em qualquer máquina, linguagem, ano.
Resistência à pré-imagem. Dado o hash, você não consegue descobrir a entrada. Não “é difícil”: é computacionalmente inviável com o que existe hoje.
Resistência à colisão. Você não consegue encontrar duas entradas diferentes que produzam o mesmo hash.
Tem uma quarta propriedade, informal e muito útil: efeito avalanche. Mudar um bit da entrada muda cerca de metade dos bits da saída. É por isso que hash serve pra detectar alteração: não existe alteração pequena.
O que ela não promete, e é aqui que dá errado
Hash não diz quem produziu o dado. Qualquer pessoa calcula o hash de qualquer coisa. Se você guarda o hash de um documento no mesmo banco que o documento, e alguém edita os dois, a verificação passa numa boa. Autoria é assinatura digital, não hash.
Hash não diz quando o dado existia. O created_at do lado é uma afirmação sua sobre você mesma. Se o requisito é provar anterioridade pra terceiro, você precisa de carimbo de tempo confiável ou de âncora pública.
Hash não protege segredo curto. Se o espaço de entradas possíveis é pequeno, dá pra testar todas. É por isso que hashear CPF pra “anonimizar” não anonimiza nada: são 11 dígitos, um notebook faz a tabela inteira numa tarde. Dado de baixa entropia hasheado continua sendo dado pessoal, inclusive pra LGPD.
Hash de senha é outra categoria. Senha usa função deliberadamente lenta e com sal, Argon2id ou bcrypt. SHA-256 puro em senha é falha, não otimização: a velocidade que faz o SHA-256 bom pra verificar integridade é exatamente o que faz ele péssimo pra guardar senha.
Escolhendo o algoritmo
Na prática, três decisões cobrem quase tudo.
Precisa de garantia criptográfica? SHA-256 é o padrão seguro e chato, e chato é elogio aqui. Suporte universal, aceleração em hardware moderno, nenhuma surpresa. BLAKE3 é bem mais rápido em volume grande, com boa reputação e menos onipresente.
Precisa só detectar corrupção acidental, sem adversário? xxHash ou CRC32. São rápidos e não são criptográficos, o que é aceitável quando ninguém está tentando te enganar.
MD5 e SHA-1 estão quebrados para uso criptográfico. Colisão de MD5 se produz em segundos. Se aparecerem no seu código pra qualquer coisa que envolva confiança, é dívida, não legado inofensivo.
O erro de produção mais comum
Não é escolher o algoritmo errado. É hashear uma representação instável do dado.
// aparentemente correto, sutilmente quebrado
const hash = sha256(JSON.stringify(payload));
Ordem de chave em objeto JavaScript não é garantida em todos os caminhos. Serializar em duas linguagens diferentes dá bytes diferentes. Float vira 1.0 num lado e 1 no outro. Adicionar um campo opcional muda tudo.
Resultado: o mesmo dado, hasheado no serviço A e verificado no serviço B, acusa divergência. Aí alguém “corrige” relaxando a verificação, e o mecanismo inteiro vira decoração.
A saída é definir uma serialização canônica explícita, versionada, com campos e ordem fixos, e hashear isso. É a diferença entre um sistema de rastreabilidade que funciona e um que só parece funcionar. Escrevi sobre esse desenho em como projetar um sistema de rastreabilidade com hash único.
Comparação em tempo constante
Quando você compara hash de coisa secreta, use comparação de tempo constante.
import { timingSafeEqual } from "node:crypto";
const ok =
a.length === b.length && timingSafeEqual(Buffer.from(a), Buffer.from(b));
Comparação normal para no primeiro byte diferente. Medindo o tempo de resposta com muitas tentativas, dá pra descobrir o valor byte a byte. Parece paranoia acadêmica, e é uma linha de código. Use em verificação de webhook, token e chave de API.
Sal e pimenta, sem mistério
Sal é um valor aleatório único por registro, guardado junto com o hash. Ele existe pra que dois usuários com a mesma senha tenham hashes diferentes, o que inutiliza tabela pré-computada.
Pimenta é um segredo global, guardado fora do banco. Se o banco vazar sem o segredo, os hashes continuam inúteis.
Sal não é secreto. Pimenta é. Confundir os dois é comum e custa caro.
O resumo prático
Hash prova que o conteúdo é o mesmo. Só isso, e isso é bastante.
Quem escreveu é assinatura. Quando existia é carimbo de tempo. Segredo curto continua descoberto. Senha pede função lenta.
Se o seu desenho depende de hash pra garantir algo além de integridade, provavelmente ele tem um buraco que ainda não apareceu.