Pular para o conteúdo
Novo

DeepSeek V4.1 Flash grátis por 7 dias: 10M tokens por dia após qualquer recarga. Até 30/11.

Como funciona

Senhas e hash com Argon2id

Por que hashes rápidos falham em senhas, o que fazem o salt e o fator de trabalho, e como gerar e verificar hashes com Argon2id em Python.

Intermediário11 min de leituraPor NotHoly

O que este artigo cobre

Bancos de senhas vazam com mais frequência do que as equipes planejam. Quando isso acontece, o atacante fica com hashes, não com senhas. O trabalho para sair de um para o outro depende de duas escolhas: a função que você usou e as configurações que deu a ela. Este artigo explica por que o armazenamento de senhas precisa de um tipo de hash diferente, o que fazem o salt e o fator de trabalho, e por que o Argon2id é a escolha que as recomendações atuais apontam. Você vai gerar e verificar o hash de uma senha em Python, ler cada parte da string codificada e recalcular o hash das senhas guardadas quando suas configurações mudarem.

Você precisa de um terminal e de Python 3. O laboratório instala um único pacote, o argon2-cffi.

Por que um hash rápido é a ferramenta errada

Uma função de hash transforma uma entrada em uma saída de tamanho fixo. O SHA-256 faz isso muito rápido, e é exatamente o que você quer para checksums e integridade de arquivos. Para senhas, a velocidade é o problema. Uma tabela de senhas roubada é atacada por tentativa: pega-se um candidato, calcula-se o hash, compara-se e repete-se. Se cada tentativa custa pouco, o atacante consegue fazer bilhões delas em placas de vídeo.

O OWASP Password Storage Cheat Sheet é direto nesse ponto. O SHA-256 é rápido, e a rapidez é exatamente o que um atacante precisa quando a entrada é uma senha, uma string curta escolhida por uma pessoa.

Você mesmo pode medir a diferença. O benchmark do laboratório calcula o hash de uma senha com SHA-256 salgado e com Argon2id, e mostra a taxa de cada um.

O que faz o salt

O salt é um valor aleatório, diferente para cada senha, guardado ao lado do hash. Ele faz duas coisas.

  • Dois usuários com a mesma senha acabam com valores armazenados diferentes. Uma tabela vazada não mostra quais contas compartilham uma senha.
  • Ele inviabiliza tabelas pré-computadas. Sem salt, o atacante calcula uma vez o hash de uma lista de senhas comuns e procura cada hash vazado. Com um salt único por usuário, cada senha precisa ser atacada separadamente.

O salt não é segredo e não torna uma senha fraca forte. A função dele é impedir que o atacante reaproveite trabalho entre usuários. A RFC 9106 recomenda um salt de 128 bits e aceita 64 bits quando o espaço é limitado. A NIST SP 800-63B exige pelo menos 32 bits. O padrão do argon2-cffi são 16 bytes, ou seja, 128 bits.

O que faz o fator de trabalho

O fator de trabalho é uma configuração que deixa cada tentativa cara de propósito. Esquemas mais antigos, como o PBKDF2, contam sobretudo iterações. O Argon2 tem três parâmetros.

  • Custo de memória (m): a RAM, em KiB, que cada hash usa.
  • Custo de tempo (t): quantas passadas a função faz sobre essa memória.
  • Paralelismo (p): quantas trilhas podem rodar ao mesmo tempo.

A memória é o que mais importa. Uma GPU tem milhares de núcleos pequenos, e cada núcleo tem pouca memória própria. Se cada tentativa exige dezenas de megabytes, o atacante não consegue rodar tantas tentativas em paralelo quanto conseguiria contra uma função que precisa de poucos kilobytes. Essa propriedade se chama dureza de memória (memory-hardness).

O fator de trabalho é um botão de ajuste, não uma constante. Aumente-o conforme o hardware fica mais rápido e conforme o orçamento de login permitir. Guarde as configurações junto de cada hash, para que hashes antigos sejam reconhecidos e atualizados depois. As últimas etapas do laboratório fazem isso.

Por que Argon2id

O Argon2 venceu a Password Hashing Competition em 2015. Ele tem três variantes.

  • O Argon2d escolhe endereços de memória com base nos dados. Resiste bem a ataques com GPU, mas esse padrão de acesso dependente dos dados pode vazar informação de tempo.
  • O Argon2i usa um padrão de acesso que não depende da entrada. Isso protege contra vazamento por tempo, mas abre mão de parte da resistência a ataques que trocam memória por tempo.
  • O Argon2id usa o padrão independente dos dados na primeira metade da primeira passada e passa para o padrão dependente depois.

O Argon2id cobre as duas preocupações. A RFC 9106 o recomenda como variante de uso geral, e o OWASP o coloca em primeiro lugar entre as opções de hash para senhas.

A RFC 9106 traz dois conjuntos de configuração recomendados. O primeiro, para sistemas com memória sobrando, usa 2 GiB, 1 passada e 4 trilhas. O segundo, para ambientes com memória limitada, usa 64 MiB, 3 passadas e 4 trilhas. O mínimo do OWASP é 19 MiB, 2 iterações e 1 trilha. O OWASP também lista alternativas que trocam memória por passadas, como 12 MiB com 3 passadas ou 7 MiB com 5 passadas, todas com 1 trilha. Os padrões do argon2-cffi coincidem com o segundo conjunto da RFC.

Trate esses valores como pisos e pontos de partida, não como metas. Uma regra prática comum é ajustar as configurações até um hash levar algumas centenas de milissegundos no seu hardware de produção. Depois, confira se a memória por hash, multiplicada pelo número de logins que podem chegar no mesmo instante, ainda cabe nos seus servidores. Essa regra vem da prática, não de nenhum dos dois padrões.

A NIST segue outro caminho. A SP 800-63B exige que as senhas sejam salgadas e tenham hash calculado com um esquema adequado, e diz que se deve usar um esquema aprovado da SP 800-132, que cobre o PBKDF2. Ela não cita o Argon2id. Se um programa de conformidade exige algoritmos aprovados pela NIST, confirme com o seu auditor antes de escolher o Argon2id.

Como o hash guardado funciona, passo a passo

Quando uma biblioteca de hash de senhas guarda uma senha, acontecem cinco coisas.

  1. A biblioteca gera um salt aleatório.
  2. Ela roda o Argon2id sobre a senha, o salt e os parâmetros de custo.
  3. Ela codifica a variante, a versão, os parâmetros, o salt e a saída em uma única string.
  4. No login, ela lê a string, extrai o salt e os parâmetros, recalcula a saída e compara as duas.
  5. Se os parâmetros da string forem mais antigos que as suas configurações atuais, você recalcula o hash depois de um login bem-sucedido.

O passo 3 é o motivo de você não precisar de uma coluna separada para o salt. A string codificada tem este formato.

text
$argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>
  • argon2id é a variante.
  • v=19 é a versão do Argon2, 1.3, escrita em decimal. Em hexadecimal, é 0x13.
  • m, t e p são memória em KiB, custo de tempo e paralelismo.
  • <salt> e <hash> estão em base64 sem preenchimento. O hash é a saída derivada, com 32 bytes por padrão.

A string do laboratório tem 97 caracteres. Use uma coluna que aceite pelo menos 255 caracteres, ou um tipo texto. Não separe a string em colunas, a menos que você goste de escrever o seu próprio parser.

Laboratório prático

O laboratório usa um ambiente virtual local e não precisa de nenhum serviço de rede. A saída abaixo vem de uma máquina Windows 11 com Python 3.12.10, argon2-cffi 25.1.0 e argon2-cffi-bindings 26.1.0. Nessa máquina, o python3 não estava no PATH do Git Bash, então os comandos rodaram como python. Use python3 se funcionar no seu. Os hashes que você vai ver serão diferentes dos mostrados, porque o salt é aleatório.

1. Crie o ambiente

bash
python3 -m venv .venv && . .venv/bin/activate
pip install argon2-cffi

No Windows, ative com .venv\Scripts\activate no PowerShell ou no cmd, ou com . .venv/Scripts/activate no Git Bash.

2. Gere o hash, verifique e confira os parâmetros

bash
python3 -c "from argon2 import PasswordHasher; ph=PasswordHasher(); h=ph.hash('correct horse battery'); print(h); print(ph.verify(h,'correct horse battery')); print(ph.check_needs_rehash(h))"

Saída observada:

text
$argon2id$v=19$m=65536,t=3,p=4$abVC6VF2EGJW1SCX/D6WBQ$XHKggONKypxBhdq7sWTbL/sMkglTAKu9y1FlL5p4dic
True
False

verify devolve True para a senha certa. check_needs_rehash devolve False porque este hash foi gerado com os padrões atuais. Esses padrões são custo de tempo 3, custo de memória 65536 KiB (64 MiB), paralelismo 4, hash de 32 bytes, salt de 16 bytes e a variante ID.

3. Detecte uma senha errada

bash
python3 -c "from argon2 import PasswordHasher; from argon2.exceptions import VerifyMismatchError; ph=PasswordHasher(); h=ph.hash('a');
try: ph.verify(h,'b')
except VerifyMismatchError: print('mismatch detected')"

Saída observada:

text
mismatch detected

Senha errada não devolve False. O verify lança VerifyMismatchError, então a checagem fica dentro de um bloco except. Uma string guardada malformada lança outra exceção, InvalidHashError. Trate isso como problema de dados do servidor e não deixe a mensagem chegar à tela do usuário.

4. Mesma senha, salts diferentes

Gere o hash da mesma senha duas vezes.

bash
python3 -c "from argon2 import PasswordHasher; ph=PasswordHasher(); h=ph.hash('correct horse battery'); print(h)"
python3 -c "from argon2 import PasswordHasher; ph=PasswordHasher(); h=ph.hash('correct horse battery'); print(h)"

Saída observada:

text
$argon2id$v=19$m=65536,t=3,p=4$p8OU0plYOCO2/v70mjYTFA$FwhMW3ECJ0F/8rulKrJ158HjyNekURguaCIfoVk0i/o
$argon2id$v=19$m=65536,t=3,p=4$MTgAgrtBHJmc1eL2iKy8nA$h/7+8J6Cdb82XTBGWsLjbYBIh05934Yel4y6tyZ6NYg

Uma senha, duas strings diferentes. Os parâmetros são iguais porque as duas execuções usaram os mesmos padrões. Só o salt e a saída mudam. Quem lê a tabela não consegue saber que essas duas contas usam a mesma senha.

5. Leia as partes da string codificada

Salve isto como parts.py.

python
from argon2 import PasswordHasher

h = PasswordHasher().hash("correct horse battery")
print(h.split("$"))

Rode com python3 parts.py. Saída observada:

text
['', 'argon2id', 'v=19', 'm=65536,t=3,p=4', 'waS+Ec4iQCNIuhRQbv2sWQ', 'yLioGsHJoVv+vTu2kRoYmXTLjbbjJSe2tPSExz3Sd5A']

O primeiro elemento vazio vem do $ inicial. Depois vêm a variante, a versão, os parâmetros, o salt e o hash. A verificação não precisa de mais nada, por isso uma coluna basta.

6. Recalcule o hash quando os parâmetros mudarem

As configurações mudam com o tempo. Este script gera um hash com configurações antigas e mais fracas e depois o confere com os padrões atuais. Salve como rehash.py.

python
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

old = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
stored = old.hash('correct horse battery')
print('stored:', stored)

new = PasswordHasher()  # padrões atuais
print('needs rehash under new params:', new.check_needs_rehash(stored))
try:
    new.verify(stored, 'correct horse battery')
    print('verify ok')
    if new.check_needs_rehash(stored):
        stored = new.hash('correct horse battery')
        print('rehashed:', stored)
except VerifyMismatchError:
    print('wrong password')
print('after rehash needs rehash:', new.check_needs_rehash(stored))

Rode python3 rehash.py. Saída observada:

text
stored: $argon2id$v=19$m=19456,t=2,p=1$EWji+p6+3HbAKY+wkykcXg$NxPq/zGPsx9okK4vQCKfZ4BVoSOqeF1QkHxd3f2g2uE
needs rehash under new params: True
verify ok
rehashed: $argon2id$v=19$m=65536,t=3,p=4$FkifxeWUHV9eU7d8JFt6mg$ZFoMkNjYqo4D/bax/jPz80Hvr4mjLWRi4TVoqCoR6ng
after rehash needs rehash: False

A ordem importa. Verifique primeiro e só depois recalcule, para que apenas uma senha correta chegue à atualização. Em uma aplicação real, grave a nova string na mesma linha logo depois de um login bem-sucedido. Se pular essa etapa, as configurações antigas ficam na tabela para sempre.

7. Meça o custo no seu hardware

Salve isto como bench.py.

python
import hashlib, time, os
from argon2 import PasswordHasher

pw = b'correct horse battery'
salt = os.urandom(16)
n = 1_000_000
t = time.perf_counter()
for _ in range(n):
    hashlib.sha256(salt + pw).digest()
dt = time.perf_counter() - t
print(f"sha256 salted: {n} hashes in {dt:.2f} s, {n/dt:,.0f} per second")

ph = PasswordHasher()
m = 10
t = time.perf_counter()
for _ in range(m):
    ph.hash('correct horse battery')
dt = time.perf_counter() - t
print(f"argon2id default: {m} hashes in {dt:.2f} s, {dt/m*1000:.0f} ms per hash")

Rode python3 bench.py. Saída observada:

text
sha256 salted: 1000000 hashes in 4.06 s, 246,343 per second
argon2id default: 10 hashes in 6.40 s, 640 ms per hash

São medições únicas em uma máquina Windows. A linha do SHA-256 mede um laço em Python, não código otimizado, então ela subestima o que um hardware dedicado faz com SHA-256. A linha do Argon2id é o custo que cada login paga. Os números exatos importam menos que a diferença. Uma chamada de SHA-256 salgado leva cerca de 4 microssegundos aqui. Uma chamada de Argon2id leva cerca de 640 milissegundos, e isso é o projeto. Ajuste m, t e p em um hardware parecido com o de produção, até que um login custe algumas centenas de milissegundos.

Erros comuns

  • Hash rápido. Usar SHA-256, SHA-512 ou MD5 para senhas. Senha não é checksum de arquivo.
  • Salt compartilhado ou previsível. Um único salt para todos os usuários, ou um salt derivado do nome de usuário. Gere um salt aleatório para cada senha.
  • Copiar configurações sem medir. Os padrões da biblioteca são um começo razoável, não uma sentença para o seu servidor. Meça antes.
  • Memória sem orçamento de concorrência. 64 MiB por login parece pouco até que 100 logins cheguem juntos, o que dá cerca de 6,4 GB. Dimensione a memória para o pico de logins simultâneos.
  • Comparar strings de hash na mão. Chame verify. Não compare strings com ==.
  • Senhas em logs. Logs, relatórios de erro e analytics nunca devem conter a senha, o hash ou o corpo completo de uma requisição de login.
  • Sem caminho de atualização. Mantenha os parâmetros dentro da string guardada e recalcule o hash no login bem-sucedido.
  • Tratar pimenta como solução. O OWASP sugere uma pimenta, um segredo guardado fora do banco, como defesa em profundidade. O cheat sheet também observa que a pimenta sozinha acrescenta pouco. Guarde-a em um cofre de segredos, se você tiver um, e não use a pimenta no lugar de um hash adequado.

Como ir além

  • Leia as seções de parâmetros e de segurança da RFC 9106 e compare com o seu modelo de ameaças.
  • Leia a documentação do argon2-cffi sobre PasswordHasher, verify e check_needs_rehash.
  • Se o seu framework web tiver um módulo de senhas, veja se ele já usa Argon2id antes de escrever um wrapper.
  • Antes de desenhar um fluxo de login, esclareça a diferença entre hash, codificação e criptografia. O artigo anterior cobre isso: criptografia, codificação e hash. O seguinte avança para pares de chaves e assinaturas: chaves públicas e privadas explicadas.

Fontes

Fontes

  1. RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
  2. OWASP Cheat Sheet Series: Password Storage Cheat Sheet
  3. NIST SP 800-63B-4: Authentication and Authenticator Management
  4. argon2-cffi documentation

Mais em Criptografia