Authentication

Password Hashing

Senhas são o único segredo que você nunca deve armazenar. Um hash lento e com salt transforma um banco de dados roubado em um monte de tentativas que nunca terminam.

intermediate14 min readUpdated 20 de set. de 2026
passwords.js
js
// passwords.js
import { hash, verify } from "@node-rs/argon2";

export const hashPassword = (password) => hash(password);

export async function verifyPassword(password, stored) {
  try {
    return await verify(stored, password);
  } catch {
    return false;
  }
}
Regra
Hash, nunca criptografe
Salt
Único por senha
Recomendado
Argon2id
Ainda comum
bcrypt
Comparação
Timing-safe
No login
Rehash se necessário

Por que importa

Por que o hashing não é opcional

Unidirecional por design

Um password hash não pode ser revertido, portanto, uma tabela vazada não revela texto simples e o atacante é forçado a tentativas offline.

Salts derrotam a pré-computação

Um salt aleatório único por senha torna as rainbow tables inúteis e garante que duas senhas idênticas resultem em hashes diferentes.

O custo é o objetivo

Um fator de custo torna cada tentativa lenta o suficiente para que a força bruta se torne impraticável, e ele pode ser aumentado conforme o hardware evolui.

O panorama completo

As três ideias por trás de credenciais seguras

Faça hash, nunca criptografe; use salt em cada digest; e torne cada tentativa deliberadamente cara.

Hash

Transformar

Uma função de derivação de chave transforma a senha em um digest de comprimento fixo que não pode ser revertido.

Salt

Randomizar

Um valor aleatório armazenado ao lado do digest torna cada hash único e derrota tabelas pré-computadas.

Verificar

Comparar

No login, derive novamente e compare em tempo constante, então atualize o hash se os parâmetros estiverem obsoletos.

HTML5 de uma olhada

Como é um bom armazenamento de credenciais

Argon2id

O padrão moderno, com memória, iterações e paralelismo ajustáveis.

bcrypt

Testado em batalha e integrado na maioria das stacks, com um fator de custo e limite de 72 bytes.

scrypt

Memory-hard e padronizado, uma boa opção quando o Argon2 não está disponível.

Salt

Aleatório por senha e armazenado na mesma string que o digest.

Comparação timing-safe

Compare digests sem vazar informações através de uma saída antecipada (early exit).

Rehash no login

Aumente o custo ou troque de algoritmo conforme os usuários fazem login.

Uma breve historia

Do crypt aos hashes memory-hard

  1. 1979

    crypt e DES

    Sistemas Unix antigos fazem hash de senhas com uma variante DES de 25 rodadas e um salt de 12 bits.

    79
  2. 1999

    bcrypt

    Provos e Mazieres introduzem um hash adaptativo baseado em Blowfish com custo ajustável.

    99
  3. 2012

    scrypt

    Uma função memory-hard é publicada para tornar o cracking paralelo em larga escala caro.

    12
  4. 2015

    Argon2 vence a PHC

    Argon2id é selecionado como o vencedor da Password Hashing Competition.

    15
  5. Hoje

    Adaptativo por padrão

    Frameworks entregam Argon2 ou bcrypt e esperam que você ajuste o fator de custo.

    Hoje

O guia completo

Password Hashing: Tudo que voce precisa saber

Por que senhas exigem tratamento especial

Qualquer outro segredo no seu sistema pode ser rotacionado: API keys, tokens, certificados. Senhas são diferentes. Você nunca as armazena em um formato que possa ser regenerado, os usuários as reutilizam em diversos sites e, no momento em que seu banco de dados vaza, o invasor tem todo o tempo do mundo para tentar adivinhá-las offline.

O hashing de senhas é a prática de armazenar uma transformação unidirecional da senha em vez da própria senha. Quando um usuário faz login, você aplica a mesma transformação e compara os resultados. Você nunca descobre a senha, e nem quem quer que roube a tabela.

Este não é o lugar para tentar ser “criativo”. As regras são simples e bem estabelecidas, e quebrá-las é o que torna as violações de segurança catastróficas.

Hashing não é criptografia

A criptografia é reversível. Se você criptografar senhas, a chave ficará armazenada em algum lugar do seu sistema, e qualquer pessoa que a obtenha — seja por meio de um vazamento, um backup, uma variável de ambiente mal configurada ou um insider — poderá ler todas as senhas de uma vez. Não existe cenário onde você precise da senha original, portanto, a reversibilidade é puro risco.

Uma função de hash recebe uma entrada e produz um digest de comprimento fixo, e não há como reverter esse processo na prática. Para senhas, um hash simples ainda não é suficiente: funções rápidas como SHA-256 são projetadas para serem velozes, que é exatamente o que um atacante deseja. Uma GPU pode testar bilhões de candidatos por segundo.

Você precisa de uma função que seja deliberadamente lenta e memory-hard, para que a tentativa de adivinhação seja cara em larga escala.

Salt: a mesma senha, um hash diferente

Sem um salt, dois usuários com a senha hunter2 produzem o mesmo digest. Um invasor pode pré-computar uma tabela gigante de senhas comuns e seus hashes — uma rainbow table — e buscar correspondências instantaneamente. Eles também conseguem notar rapidamente quais contas compartilham a mesma senha.

Um salt é um valor aleatório único gerado por senha e armazenado junto ao digest. Ele não é secreto; sua única função é tornar cada hash único. Agora, o invasor precisa atacar cada conta separadamente, tornando a pré-computação inútil.

Anatomy of a stored Argon2 hash
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$aBc...
algorithm + paramsalgorithm, version and work factors
saltunique per password, not secret
digestthe derived key you compare against

Bibliotecas modernas geram o salt para você e o codificam, junto com os parâmetros, em uma única string. Você armazena essa string exatamente como ela é.

Escolhendo um algoritmo

Existem três funções que valem a pena conhecer. Todas são adaptativas: o custo pode ser aumentado conforme o hardware evolui.

Argon2id

O vencedor da Password Hashing Competition de 2015 e a recomendação atual. Ele é memory-hard, o que significa que um atacante precisa investir em memória além de processamento, reduzindo a vantagem de GPUs e ASICs. O Argon2id equilibra a resistência a ataques de canal lateral (side-channel) e ataques de GPU, sendo a escolha padrão ideal para novos sistemas.

scrypt

Padronizado na RFC 7914 e também memory-hard, o scrypt é uma escolha sólida quando o Argon2 não está disponível. Ele expõe custo, tamanho do bloco e paralelismo, permitindo ajustes, mas seus parâmetros são mais fáceis de configurar incorretamente do que os do Argon2.

bcrypt

Lançado em 1999 e ainda onipresente, o bcrypt é bem compreendido e testado em batalha. Seu fator de custo dobra o trabalho a cada incremento. A principal ressalva é que ele trunca a entrada em 72 bytes, portanto, frases de senha muito longas são cortadas silenciosamente — faça um pré-hash se isso for importante para você.

Independentemente da sua escolha, nunca utilize MD5, SHA-1 ou SHA-256 puro. Eles são as ferramentas erradas, e empilhar rodadas de hashing sobre eles é um esquema caseiro em que ninguém deve confiar.

Ajustando o fator de trabalho (work factor)

O custo deve equilibrar duas pressões. Se for muito baixo, o hash fica vulnerável a ataques de força bruta; se for muito alto, um pico de tentativas de login pode se tornar um vetor de negação de serviço (DoS) contra a sua própria CPU.

Ajuste-o para que um único hash leve aproximadamente 100 a 500 milissegundos no seu hardware de produção. Meça o endpoint de login sob carga realista, e não apenas em um loop de benchmark, e defina o número com base nesses dados. Depois, revise-o periodicamente: os mesmos parâmetros tornam-se mais fracos a cada ano conforme o hardware evolui.

// Argon2id parameters (memory in KiB, iterations, parallelism)
await hash(password, { memoryCost: 19456, timeCost: 2, parallelism: 1 });

Armazenando e verificando

O registro e o login são os únicos dois lugares onde você manipula uma senha.

import { hash, verify } from "@node-rs/argon2";

// Registration: hash before the password ever reaches the database.
const digest = await hash(password);
await db.users.insert({ email, passwordHash: digest });

// Login: verify against the stored digest.
const user = await db.users.findByEmail(email);
const ok = user && (await verify(user.passwordHash, password));
if (!ok) return res.status(401).json({ error: "invalid_credentials" });

Retorne o mesmo erro, independentemente de o e-mail ou a senha estarem incorretos. Uma mensagem como “usuário não encontrado” oferece a um invasor uma maneira de enumerar contas.

Ataques de timing e comparação segura

Comparar dois hashes com === interrompe o processo no primeiro byte diferente. Portanto, o tempo gasto depende de quanto do palpite estava correto, e um atacante paciente pode medir isso para reconstruir um valor. É um canal estreito, mas a comparação de senhas é exatamente onde isso importa.

Use a função de verificação da biblioteca, que realiza a comparação em tempo constante. Nunca compare os digests por conta própria e nunca compare a senha em texto puro.

Atualizando hashes com o tempo

Você mudará de ideia: o custo de processamento aumentará ou você migrará do bcrypt para o Argon2. Como você nunca armazenou o texto simples, a única maneira de gerar um novo hash é durante o login, quando a senha está brevemente na memória.

Armazene o algoritmo e os parâmetros com cada digest — a string codificada já faz isso — e verifique-os a cada login bem-sucedido:

if (await verify(stored, password)) {
  if (needsRehash(stored)) {
    await db.users.update(user.id, { passwordHash: await hash(password) });
  }
  // continue the login
}

Usuários que nunca mais fizerem login manterão o hash antigo, o que não é um problema; eles continuam protegidos. Com o tempo, as contas ativas migram naturalmente.

Política de senhas na prática

O algoritmo protege você após uma violação. A política reduz a chance de que ela ocorra.

  • Prefira comprimento em vez de complexidade. Uma frase-senha (passphrase) é melhor que P@ssw0rd!.
  • Verifique novas senhas contra listas de vazamentos conhecidos, como a API de k-anonymity do Have I Been Pwned.
  • Não imponha rotações arbitrárias. A expiração forçada leva a padrões de Summer2026!.
  • Aplique rate-limit em tentativas de login e reset, e adicione exponential backoff ou lockout.
  • Use um fluxo de reset genérico com tokens de uso único e com data de expiração, e nunca envie senhas por e-mail.

Melhores práticas

  • Faça o hash com Argon2id, scrypt ou bcrypt — nunca utilize um hash rápido de propósito geral.
  • Deixe que a biblioteca gere e armazene o salt.
  • Ajuste o custo para entre 100 e 500 ms e aumente esse valor com o tempo.
  • Verifique utilizando a função de tempo constante da biblioteca.
  • Faça o rehash após um login bem-sucedido quando os parâmetros mudarem.
  • Retorne erros genéricos e aplique rate-limit nas tentativas.
  • Mantenha o hashing no servidor; nunca no cliente.

Erros comuns

  • Criptografar senhas ou armazená-las em texto simples “temporariamente”.
  • Usar SHA-256 ou MD5 com poucas rodadas e chamar isso de hashing.
  • Utilizar um único salt global ou reutilizar o mesmo salt entre contas.
  • Comparar hashes com == ou ===.
  • Registrar senhas em logs ou incluí-las em relatórios de erro.
  • Limitar o comprimento da senha a um valor baixo, o que prejudica o uso de passphrases.
  • Esquecer de atualizar o fator de custo por anos.

Próximos passos

As credenciais são apenas a primeira etapa. Uma vez que o usuário seja verificado, você precisará de um lugar para manter esse estado: leia o guia de Session Auth para sessões baseadas em cookies, ou o guia de JWT para tokens stateless. Quando você preferir não lidar com senhas, o OAuth 2.0 e o OpenID Connect delegam o login a um provedor de identidade.

Na pratica

Hash, armazene, verifique

Os mesmos três passos em qualquer stack. A biblioteca gerencia o salt e o codifica junto ao digest.

hash.js
import { hash } from "@node-rs/argon2";

const digest = await hash(password);
// $argon2id$v=19$m=19456,t=2,p=1$...$...

Fazendo hash de uma senha

Use um password hash criado para esse propósito com um fator de custo. Um hash rápido de propósito geral é o presente favorito de um cracker.

Preferir
import { hash } from "@node-rs/argon2";

const digest = await hash(password);
Evitar
import { createHash } from "node:crypto";

const digest = createHash("sha256")
  .update(password)
  .digest("hex");

Comparando uma senha

Compare através da verificação de tempo constante da biblioteca. A igualdade de strings simples pode vazar conteúdo e comprimento via timing.

Preferir
const ok = await verify(stored, password);
Evitar
const ok = stored === (await hash(password));

Trade-offs

Argon2 ou bcrypt?

Ambos são seguros quando ajustados. Argon2 é o padrão moderno; bcrypt continua sendo uma escolha perfeitamente válida com anos de uso em produção.

Strengths

  • Argon2id é memory-hard

    Ele resiste ao cracking por GPU e ASIC ao exigir memória além de tempo, sendo a recomendação atual.

  • bcrypt está em todo lugar

    Está integrado na maioria dos frameworks, possui um longo histórico e não precisa de dependências nativas extras.

  • Ambos são ajustáveis

    O fator de custo pode ser aumentado com o tempo, permitindo que hashes armazenados sejam fortalecidos sem alterar as senhas dos usuários.

Trade-offs

  • bcrypt trunca em 72 bytes

    Passphrases longas são cortadas silenciosamente, então faça um pre-hash ou prefira Argon2 quando isso for importante.

  • O ajuste exige cuidado

    Muito baixo e torna-se vulnerável; muito alto e o login torna-se um vetor de negação de serviço (DoS) sob carga.

  • Hashing não é a história completa

    Rate limiting, verificações de vazamentos e um fluxo de reset seguro importam tanto quanto o algoritmo.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender Password Hashing & Credentials?

Nosso tutorial interativo te guia por Password Hashing & Credentials passo a passo — com quizzes e codigo real que voce pode executar no navegador.