Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Argon2 no Node.js: Senhas Seguras

Atualizado em: 29 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Usar Argon2 no Node.js é uma das formas mais seguras de armazenar senhas em aplicações modernas. Em vez de guardar a senha original ou aplicar um hash rápido, como SHA-256, a aplicação utiliza uma função deliberadamente lenta e intensiva em memória. Isso aumenta muito o custo de ataques offline quando um banco de dados é vazado.

O objetivo não é tornar o login perceptivelmente lento, mas fazer cada tentativa de quebra consumir memória e processamento suficientes para dificultar milhões de palpites por segundo. A implementação correta também exige salt aleatório, parâmetros ajustados ao servidor, limites de requisição, comparação segura, migração de hashes antigos e uma política de recuperação de conta.

Neste guia, você aprenderá a usar Argon2id, criar hashes, verificar senhas, ajustar memória e tempo, armazenar o resultado, migrar usuários, aplicar pepper, limitar tentativas e testar o fluxo de autenticação.

Por que senhas não devem ser criptografadas?

Criptografia é reversível quando a chave está disponível. Para autenticação comum, o servidor não precisa recuperar a senha original. Ele precisa apenas confirmar se a senha informada produz o mesmo resultado esperado.

Por isso, senhas devem ser processadas por uma função de derivação resistente a ataques, e não por uma cifra reversível. A referência Password Storage Cheat Sheet da OWASP reúne recomendações práticas. O RFC 9106 descreve o Argon2.

Por que SHA-256 não basta?

SHA-256 foi projetado para ser rápido. Essa característica é excelente para integridade de arquivos, assinaturas e outras tarefas, mas é ruim para senhas. Um atacante com hashes vazados pode testar enormes quantidades de combinações usando GPUs e hardware especializado.

Argon2 permite configurar custo de memória, número de iterações e paralelismo. Assim, cada tentativa exige recursos significativos.

Argon2id

Existem variantes do Argon2. Para armazenamento de senhas, Argon2id é normalmente a escolha preferida porque combina resistência a ataques de canal lateral com proteção contra hardware paralelo.

Não invente uma variante própria. Use uma biblioteca mantida que implemente o formato completo, incluindo salt, parâmetros e versão.

Instalando a biblioteca

npm install argon2

Bibliotecas nativas podem exigir ferramentas de compilação em algumas plataformas. Em ambientes Docker, confirme que a imagem possui as dependências necessárias ou que o pacote oferece binários compatíveis.

Criando um hash

import argon2 from 'argon2';

export async function hashPassword(password) {
  return argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 19_456,
    timeCost: 2,
    parallelism: 1
  });
}

Os valores acima são apenas um ponto de partida. O ajuste ideal depende da capacidade do servidor, do volume de logins e do risco da aplicação.

Formato armazenado

O resultado costuma conter algoritmo, versão, parâmetros, salt e hash:

$argon2id$v=19$m=19456,t=2,p=1$...$...

Armazene a string completa em uma coluna suficientemente grande. Não separe o salt manualmente, a menos que a biblioteca exija.

Verificando a senha

export async function verifyPassword(hash, password) {
  try {
    return await argon2.verify(hash, password);
  } catch {
    return false;
  }
}

A biblioteca interpreta os parâmetros incorporados no hash e realiza a comparação corretamente.

Fluxo de login

const user = await users.findByEmail(normalizedEmail);

const hash = user?.passwordHash
  ?? DUMMY_PASSWORD_HASH;

const valid = await verifyPassword(hash, password);

if (!user || !valid) {
  throw new InvalidCredentialsError();
}

Usar um hash fictício quando o usuário não existe ajuda a reduzir diferenças de tempo entre “e-mail inexistente” e “senha incorreta”.

Mensagem de erro genérica

{
  "code": "INVALID_CREDENTIALS",
  "message": "E-mail ou senha inválidos"
}

Não revele qual campo falhou. Isso dificulta enumeração de contas.

Normalização da senha

Evite transformar silenciosamente a senha. Espaços, letras maiúsculas e caracteres Unicode podem ser intencionais. A política deve ser definida na criação da conta e permanecer consistente.

Para e-mail, uma normalização controlada pode ser adequada. Para senha, preserve exatamente o valor informado, depois de validar tamanho e encoding.

Limite de tamanho

Senhas enormes podem causar consumo excessivo de memória e processamento. Defina um limite de bytes razoável, por exemplo 1.024 bytes, sem impor um máximo artificialmente pequeno.

const passwordBytes = Buffer.byteLength(password, 'utf8');

if (passwordBytes < 12 || passwordBytes > 1024) {
  throw new ValidationError('Senha inválida');
}

Comprimento mínimo

Prefira senhas longas e permita frases. Regras como exigir símbolo, número e letra maiúscula podem gerar padrões previsíveis. Um comprimento mínimo adequado, bloqueio de senhas comprometidas e suporte a gerenciadores de senha costumam ser mais úteis.

Salt

O salt deve ser único e aleatório para cada hash. Bibliotecas de Argon2 normalmente o geram automaticamente. Não use e-mail, ID do usuário ou timestamp como salt.

O salt não é secreto. Ele existe para impedir que hashes iguais apareçam para usuários com a mesma senha e para inviabilizar tabelas pré-calculadas.

Pepper

Um pepper é um segredo adicional mantido fora do banco. Ele pode ser combinado à senha antes do Argon2 ou aplicado com HMAC ao resultado, conforme o desenho escolhido.

const prepared = createHmac('sha256', pepper)
  .update(password, 'utf8')
  .digest();

const hash = await argon2.hash(prepared, options);

O pepper deve ficar em um gerenciador de segredos, KMS ou Secret da plataforma. Se ele for perdido, as senhas não poderão ser verificadas.

Rotação do pepper

Rotacionar o pepper exige estratégia. Uma opção é manter versões e atualizar o hash após um login válido. Outra é forçar redefinição de senha em um incidente crítico.

Nunca coloque o pepper no mesmo banco que os hashes.

Ajustando parâmetros

Meça o tempo de hash na infraestrutura real. Um alvo comum é tornar a operação suficientemente cara sem causar uma experiência ruim. Considere picos de login, autoscaling, memória por processo e concorrência.

const startedAt = performance.now();
await hashPassword('senha-de-teste');
console.log(performance.now() - startedAt);

Custo de memória

O parâmetro de memória dificulta ataques em GPU, mas também afeta seu servidor. Se cada hash usa dezenas de megabytes e cem logins ocorrem ao mesmo tempo, o consumo pode crescer rapidamente.

Time cost

O número de iterações aumenta o trabalho realizado. Ele deve ser ajustado junto ao custo de memória, e não isoladamente.

Paralelismo

Mais paralelismo pode aproveitar múltiplos núcleos, mas aumenta concorrência. Em servidores com muitas requisições, um valor baixo costuma oferecer comportamento previsível.

Fila ou limite de concorrência

Não permita milhares de hashes simultâneos. Use rate limiting e, quando necessário, um semáforo interno para limitar operações concorrentes.

await passwordHashSemaphore.run(async () => {
  return hashPassword(password);
});

Rate limiting no login

Limite tentativas por conta, IP confiável e dispositivo. Consulte Rate Limiting no Node.js.

Evite bloqueio permanente simples, pois um atacante pode negar acesso ao usuário. Use atraso progressivo, desafios adicionais e alertas.

Rehash automático

Parâmetros recomendados evoluem. Depois de verificar uma senha, confira se o hash precisa ser atualizado:

if (argon2.needsRehash(user.passwordHash, currentOptions)) {
  const newHash = await hashPassword(password);
  await users.updatePasswordHash(user.id, newHash);
}

Faça a atualização em segundo plano controlado ou dentro de uma transação curta.

Migração de bcrypt

Não é possível converter um hash bcrypt em Argon2 sem a senha original. Migre no próximo login:

  1. Identifique o algoritmo pelo prefixo.
  2. Verifique com a biblioteca antiga.
  3. Se válido, gere Argon2id.
  4. Substitua o hash.
  5. Registre a migração sem incluir a senha.

Usuários inativos

Contas que nunca voltam a entrar permanecem com o hash antigo. Para uma mudança crítica, force redefinição de senha após determinado prazo.

Cadastro

Antes de criar o hash, valide política, verifique senha comprometida quando houver serviço adequado e confirme que a requisição está protegida por HTTPS.

Não registre a senha em logs, analytics, rastreamento de erro ou eventos de auditoria.

Recuperação de senha

O fluxo de recuperação deve usar token aleatório, de uso único e curta duração. Depois da redefinição, revogue sessões e refresh tokens.

Consulte Sessões Seguras no Node.js e Refresh Tokens no Node.js.

MFA e passkeys

Hash forte protege a senha armazenada, mas não impede phishing. Ofereça MFA e, para maior resistência, passkeys. Veja Passkeys no Node.js e TOTP no Node.js.

Banco de dados

ALTER TABLE users
ADD COLUMN password_hash TEXT,
ADD COLUMN password_changed_at TIMESTAMPTZ,
ADD COLUMN password_algorithm TEXT;

O campo de algoritmo pode ajudar na migração, embora o prefixo do hash já carregue essa informação.

Permissões do banco

A aplicação precisa ler hashes para verificar login, portanto controle estritamente acesso à tabela. Ferramentas administrativas e réplicas analíticas não devem receber essa coluna sem necessidade.

Logs

Registre resultado, duração, algoritmo e necessidade de rehash, mas nunca senha, hash completo ou pepper.

Para logging estruturado, consulte Logs com Pino no Node.js.

Métricas

Monitore tempo de hash, falhas, fila, uso de memória, rehashes e bloqueios. Não use e-mail ou user ID como label de alta cardinalidade.

Teste unitário

test('verifica a senha correta', async () => {
  const hash = await hashPassword('frase longa e segura');

  assert.equal(
    await verifyPassword(hash, 'frase longa e segura'),
    true
  );

  assert.equal(
    await verifyPassword(hash, 'senha errada'),
    false
  );
});

Teste de salt

Gere dois hashes da mesma senha e confirme que são diferentes, mas ambos verificam corretamente.

Teste de migração

Crie uma fixture bcrypt, autentique e confirme que o banco termina com Argon2id.

Teste de rehash

Use parâmetros antigos em uma fixture e confirme atualização depois do login válido.

Teste de carga

Meça o comportamento com múltiplos logins concorrentes. Observe memória, CPU, latência p95 e event loop.

Erros comuns

  • Usar SHA-256: o hash é rápido demais.
  • Salt fixo: senhas iguais produzem resultados relacionados.
  • Parâmetros copiados sem benchmark: o servidor pode ficar vulnerável ou instável.
  • Senha em logs: a credencial é exposta.
  • Pepper no banco: a camada adicional perde valor.
  • Sem rate limiting: o endpoint vira ferramenta de ataque online.
  • Não migrar hashes antigos: contas permanecem com proteção inferior.

Boas práticas

  • Use Argon2id.
  • Deixe a biblioteca gerar o salt.
  • Ajuste parâmetros com benchmark.
  • Limite concorrência e tentativas.
  • Armazene a string completa do hash.
  • Mantenha pepper fora do banco.
  • Atualize hashes após login.
  • Revogue sessões após redefinição.
  • Nunca registre senhas.
  • Ofereça MFA ou passkeys.

Conclusão

Usar Argon2 no Node.js reduz significativamente o impacto de um vazamento de hashes. O algoritmo força cada tentativa de quebra a consumir memória e processamento, dificultando ataques em grande escala.

A segurança depende do conjunto completo: Argon2id, salt aleatório, parâmetros medidos, pepper protegido, rate limiting e migração contínua. Com recuperação segura, revogação de sessões e autenticação resistente a phishing, a senha deixa de ser um ponto único de falha.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita