Criptografia de Senhas em Python: Hashing Seguro com Argon2
Aprenda por que senhas não devem ser criptografadas e implemente hashing seguro em Python com Argon2id, salt, verificação e migração.

Quem procura por criptografia de senhas em Python geralmente quer impedir que credenciais roubadas sejam lidas.
A intenção está correta, mas o mecanismo adequado não é criptografar a senha: é aplicar uma função de hashing de senhas lenta, irreversível e com salt único.
Em projetos novos, Argon2id é a escolha preferencial indicada pelo OWASP.
Neste guia você vai implementar cadastro e verificação com argon2-cffi, entender o que guardar no banco, atualizar parâmetros sem obrigar o usuário a trocar a senha e planejar a migração de hashes antigos.
O objetivo é sair com um fluxo aplicável a APIs e sistemas reais, sem depender de SHA-256 puro, MD5 ou de exemplos que armazenam segredos de forma insegura.
Senha deve ser criptografada ou transformada em hash?
Senhas de usuários devem ser hasheadas, não criptografadas. Criptografia é reversível por quem possui a chave. Isso é necessário quando o sistema precisa recuperar o conteúdo original, como ocorre com determinados documentos.
Na autenticação, porém, a aplicação não precisa conhecer a senha novamente: precisa apenas confirmar se o valor informado corresponde ao cadastro.
A função de hashing recebe a senha e produz um valor derivado que não deve permitir a recuperação prática do texto original.
No login, a biblioteca lê do próprio hash os parâmetros e o salt, repete o cálculo com a senha recebida e faz uma comparação segura. Se os resultados correspondem, a identidade pode avançar para as próximas verificações.
Portanto, “criptografia de senhas” continua sendo uma expressão útil para pesquisa, mas a decisão técnica correta é armazenamento com uma função específica para senhas.
Se você ainda está consolidando os fundamentos da linguagem, vale revisar a introdução ao Python antes de integrar o exemplo a uma aplicação maior.
Qual ameaça o armazenamento de senhas precisa enfrentar?
O cenário decisivo é o ataque offline. Imagine que uma falha expôs a tabela de usuários.
O invasor agora pode testar milhões de combinações no próprio hardware, sem passar pelo limite de tentativas da sua API.
Um hash rápido facilita essa busca; um algoritmo lento e com alto custo de memória torna cada tentativa mais cara.
Salt exclusivo impede que duas contas com a mesma senha tenham o mesmo valor armazenado e reduz a utilidade de tabelas pré-computadas. Custo configurável permite aumentar o trabalho conforme o hardware evolui.
Uso intensivo de memória dificulta a vantagem de paralelizar tentativas em GPUs e equipamentos especializados. Esses controles não tornam senhas fracas invencíveis, mas aumentam significativamente o custo de quebra.
Também é necessário proteger o cadastro e o login contra ataques online. É por isso que hashing deve conviver com TLS, limitação de tentativas, monitoramento e autenticação multifator.
Para o lado do usuário, um guia sobre como criar senhas fortes e gerenciáveis ajuda a reduzir o risco que nenhum algoritmo consegue eliminar sozinho.
Hash, criptografia e codificação não são a mesma coisa
| Mecanismo | É reversível? | Uso correto | Serve para armazenar senhas? |
|---|---|---|---|
| Hashing de senha | Projetado para não ser | Verificar um segredo sem guardar o original | Sim, com Argon2id, scrypt, bcrypt ou PBKDF2 |
| Criptografia | Sim, com a chave | Proteger dados que precisam ser recuperados | Não como mecanismo principal |
| Codificação | Sim, sem segredo | Representar ou transportar dados | Não |
Base64, por exemplo, apenas muda a representação e pode ser revertido por qualquer pessoa.
Criptografar a senha transfere todo o risco para a chave: se ela vazar junto com o banco, todas as credenciais ficam recuperáveis.
O hash de senha foi desenhado justamente para que o servidor valide sem manter um caminho de volta ao texto original.
Por que SHA-256, SHA-1 e MD5 não protegem senhas
SHA-256 é apropriado para verificar integridade e compor outros protocolos, mas foi feito para ser rápido. Essa qualidade vira um defeito no banco de credenciais: o invasor também consegue testar candidatos rapidamente.
Acrescentar um salt manual evita resultados idênticos, porém não transforma SHA-256 puro em uma função lenta e resistente a hardware paralelo.
MD5 e SHA-1 têm problemas adicionais e não devem ser escolhidos para novas implementações.
Fazer várias rodadas caseiras, concatenar strings ou inventar um formato próprio também aumenta a chance de erros. O caminho seguro é adotar uma biblioteca mantida e um algoritmo desenvolvido para derivação de senha.
Em sistemas legados, o hash antigo pode ser aceito temporariamente apenas para migração após um login válido.

Por que usar Argon2id em Python
Argon2 venceu a Password Hashing Competition e foi padronizado no RFC 9106. A variante Argon2id combina características de Argon2i e Argon2d para equilibrar resistência a ataques de canal lateral e a ataques acelerados por hardware.
O OWASP Password Storage Cheat Sheet recomenda Argon2id para novos sistemas.
Em Python, argon2-cffi oferece uma API de alto nível chamada PasswordHasher. Ela gera salt aleatório, produz a string codificada com algoritmo, versão e parâmetros, compara a senha e informa quando o hash precisa ser atualizado.
Isso reduz a quantidade de decisões criptográficas que o código da aplicação precisa tomar.
Como criar hashing de senhas em Python com argon2-cffi
Instale a biblioteca no ambiente virtual do projeto e registre a dependência no arquivo usado pela sua equipe:
python -m pip install argon2-cffi
Crie uma instância de PasswordHasher e use hash() quando a conta for cadastrada ou quando o usuário alterar a senha:
from argon2 import PasswordHasher
password_hasher = PasswordHasher()
def gerar_hash(senha: str) -> str:
if not senha:
raise ValueError("A senha não pode ser vazia")
return password_hasher.hash(senha)
hash_para_salvar = gerar_hash("uma-senha-fornecida-pelo-usuario")
Não imprima a senha nem o hash em logs. A string retornada começa com a identificação do Argon2id e inclui versão, custos, salt e resultado codificados.
Ela pode ser persistida inteira em uma coluna de texto. Não há necessidade de criar outra coluna para salt nem de chamar um gerador manual, pois a API de alto nível já faz isso a cada chamada.
Em uma API, valide tamanho e política na borda, passe a senha para um serviço de autenticação e descarte a referência assim que possível.
O artigo sobre API RESTful em Python com Flask ajuda a situar esse serviço no fluxo de uma aplicação web, mas evite colocar a regra criptográfica diretamente na função de rota.
Como verificar a senha no login
No login, busque o hash associado ao identificador normalizado da conta e entregue o hash e a senha informada para verify(). A biblioteca extrai os parâmetros da string armazenada.
Senha incorreta gera uma exceção específica; hash inválido ou outra falha de verificação também deve resultar em autenticação negada, sem revelar detalhes ao cliente.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, VerificationError
password_hasher = PasswordHasher()
def verificar_senha(hash_armazenado: str, senha_informada: str) -> bool:
try:
return password_hasher.verify(hash_armazenado, senha_informada)
except (VerifyMismatchError, VerificationError):
return False
A resposta pública deve ser genérica, como “credenciais inválidas”, independentemente de o e-mail existir. Internamente, registre métricas sem incluir segredos.
Compare usando a função da biblioteca; não extraia os componentes para fazer igualdade de strings por conta própria.
Como atualizar o hash sem redefinir a senha
Parâmetros seguros mudam com o tempo. Depois de verificar uma senha corretamente, check_needs_rehash() informa se o valor foi criado com configurações diferentes das atuais.
Nesse caso, gere outro hash usando a senha que acabou de ser validada e substitua a coluna na mesma transação lógica do login.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, VerificationError
password_hasher = PasswordHasher()
def verificar_e_atualizar(
hash_armazenado: str,
senha_informada: str,
) -> tuple[bool, str | None]:
try:
password_hasher.verify(hash_armazenado, senha_informada)
except (VerifyMismatchError, VerificationError):
return False, None
novo_hash = None
if password_hasher.check_needs_rehash(hash_armazenado):
novo_hash = password_hasher.hash(senha_informada)
return True, novo_hash
O chamador salva novo_hash somente quando ele não é None. Essa atualização gradual evita processar toda a base de uma só vez e não exige conhecer a senha fora do momento de autenticação.
Contas que não retornam por muito tempo podem receber uma redefinição obrigatória conforme a política de risco.
O que armazenar no banco de dados
- O hash codificado completo retornado pela biblioteca.
- O identificador da conta, separado da credencial e normalizado de forma consistente.
- Metadados operacionais realmente necessários, como data de troca ou estado de bloqueio.
- Nunca a senha em texto puro, uma cópia “temporária”, uma pergunta secreta equivalente ou conteúdo em log.
Dimensione a coluna com folga para mudanças de algoritmo e parâmetros.
O formato codificado preserva os dados necessários para a verificação e para uma migração progressiva.
Backups, réplicas e ambientes de análise merecem o mesmo controle de acesso do banco principal, pois um vazamento de hashes ainda habilita ataques offline.
Salt e pepper: funções e diferenças
Salt é um valor aleatório e único para cada hash. Não precisa ser secreto e acompanha o resultado armazenado.
Sua função é fazer com que senhas iguais produzam hashes diferentes e impedir o reaproveitamento eficiente de resultados pré-computados.
Com PasswordHasher, a geração e a inclusão do salt já acontecem automaticamente.
Pepper é um segredo adicional opcional compartilhado pela aplicação. Ao contrário do salt, ele não deve ficar no mesmo banco.
Se o modelo de ameaça justificar seu uso, mantenha-o em um gerenciador de segredos ou módulo apropriado, planeje rotação e restrinja acesso.
Um arquivo .env pode ajudar em desenvolvimento, como mostra o guia de variáveis com dotenv, mas produção exige governança de segredos, não apenas um arquivo fora do Git.
O pepper acrescenta complexidade: se for perdido, os hashes podem se tornar inutilizáveis; se precisar ser trocado, normalmente será necessário reprocessar após login ou redefinir senhas.
Ele é uma camada adicional, não substitui Argon2id, salt único, proteção do banco e controle de acesso.
Como definir os parâmetros de custo
Argon2id combina custo de memória, número de iterações e paralelismo. O OWASP publica mínimos de referência, mas o valor adequado depende do hardware, da concorrência e da latência aceitável no seu sistema.
Comece com os parâmetros seguros da versão atual da biblioteca, execute benchmarks no ambiente de produção ou equivalente e monitore consumo em picos.
Aumentar o custo sem medir pode permitir negação de serviço: cada tentativa legítima ou maliciosa consome recursos do servidor. Defina limite de concorrência, aplique rate limiting e teste o tempo de resposta nos menores contêineres ou instâncias utilizados. Guarde algoritmo e parâmetros no formato do hash para que valores antigos continuem verificáveis durante a transição.
Reavalie periodicamente, especialmente após atualização de infraestrutura ou de biblioteca.
A documentação oficial da API do argon2-cffi descreve PasswordHasher, verify() e check_needs_rehash(); use-a como referência para o comportamento da versão instalada.
Argon2id, scrypt, bcrypt ou PBKDF2?
| Algoritmo | Quando considerar | Atenção principal |
|---|---|---|
| Argon2id | Primeira escolha para projetos novos | Medir memória, tempo e concorrência |
| scrypt | Alternativa quando Argon2id não está disponível | Configurar custo de memória adequadamente |
| bcrypt | Manutenção de sistemas existentes | Limite de entrada de 72 bytes e custo calibrado |
| PBKDF2-HMAC-SHA-256 | Ambientes com requisitos de conformidade específicos, como módulos validados | Exige número alto de iterações e revisão periódica |
Não troque um algoritmo moderno apenas porque outro parece mais popular em um tutorial. Considere requisitos regulatórios, suporte da plataforma, estratégia de migração e capacidade de operação.
Bcrypt ainda pode ser verificado durante uma migração, mas sua limitação de entrada precisa ser entendida; truncar silenciosamente uma senha é um erro.
Como migrar hashes antigos com segurança
Um hash é irreversível, então não existe conversão direta de SHA-256 ou bcrypt para Argon2id sem conhecer a senha. A migração mais segura ocorre no login:
- Identifique o algoritmo pela coluna de versão ou pelo formato do valor.
- Verifique a senha com o método legado exclusivamente para aquela conta.
- Se a verificação for válida, gere imediatamente um hash Argon2id.
- Substitua o valor antigo e registre apenas o evento de migração, nunca a senha.
- Se a verificação falhar, responda com erro genérico e não altere o banco.
Separe o verificador legado em código pequeno, testado e com prazo de remoção. Para contas inativas, considere expirar a credencial e exigir redefinição.
Não faça “hash do hash” como solução permanente: isso preserva limitações do material antigo, dificulta auditoria e não recupera a entropia perdida.

Defesa em profundidade no fluxo de autenticação
Hashing forte limita o dano de um banco vazado, mas não corrige phishing, reutilização de senha, roubo de sessão ou uma rota sem proteção contra tentativas automatizadas.
A arquitetura deve combinar controles independentes:
- TLS em todo o tráfego, inclusive entre serviços quando aplicável.
- Rate limiting por conta, origem e sinais de risco, com cuidado para não facilitar bloqueio abusivo.
- Autenticação multifator e opções modernas de segundo fator; veja os aplicativos para autenticação.
- Sessões com cookies seguros, expiração, rotação e revogação.
- Mensagens genéricas em login e recuperação para reduzir enumeração de usuários.
- Monitoramento de anomalias e resposta a incidentes.
- Privilégio mínimo para aplicação, equipe, backups e banco.
As diretrizes atuais do NIST SP 800-63B também orientam armazenamento com salt, esquema adequado para senhas, fator de custo e limitação de tentativas.
Se houver incidente, o plano precisa considerar notificação, rotação de segredos e análise do alcance; o guia sobre proteção contra vazamento de dados amplia esse contexto.
Testes e checklist de implementação
- Dois hashes da mesma senha são diferentes por causa dos salts únicos.
- A senha correta é aceita e uma senha diferente é rejeitada.
- Hash truncado, malformado ou de tipo inesperado não autentica e não derruba a aplicação.
check_needs_rehash()atualiza somente depois de uma verificação válida.- Senha, hash e pepper não aparecem em logs, rastreamento, mensagens de erro ou eventos analíticos.
- O banco armazena a string completa sem truncamento.
- Rate limiting funciona sob concorrência e não depende apenas do endereço IP.
- Backup e réplica têm criptografia, acesso e retenção revisados.
- A recuperação de conta não é mais fraca que o login.
- Dependências recebem atualizações e a configuração é reavaliada com benchmark.
Inclua testes de integração do cadastro ao login e da migração legada. Use senhas fictícias nos testes e nunca copie amostras de produção.
Uma revisão específica de segurança deve confirmar o modelo de ameaça, porque aprovação em testes funcionais não prova que o custo está adequado ou que os segredos estão bem isolados.
Erros comuns ao proteger senhas em Python
- Usar
hashlib.sha256()diretamente: é rápido demais para armazenamento de senha. - Reutilizar salt: o salt deve ser aleatório e único por hash.
- Inventar o próprio algoritmo: pequenas decisões podem anular a proteção.
- Criptografar para depois descriptografar: cria um ponto único de falha na chave.
- Guardar pepper junto do banco: isso elimina a separação que justificaria a camada.
- Ignorar limites de recurso: custo exagerado sem rate limiting pode causar indisponibilidade.
- Registrar credenciais: filtros de log e observabilidade precisam tratar campos sensíveis.
- Manter hashes antigos indefinidamente: toda compatibilidade legada deve ter plano de saída.
Outro erro é pensar que “criptografia forte” compensa falhas no restante da aplicação. Uma chave exposta, dependência vulnerável ou acesso excessivo pode comprometer o sistema.
O artigo sobre riscos de criptografia fraca mostra por que algoritmo, configuração e gestão operacional precisam ser avaliados em conjunto.
Perguntas frequentes
Posso descriptografar um hash Argon2id?
Não. O hash é uma transformação unidirecional. Para autenticar, a biblioteca verifica se a senha informada corresponde ao hash; para recuperar acesso, o sistema deve oferecer um fluxo seguro de redefinição.
Preciso armazenar o salt em segredo?
Não. O salt precisa ser único e imprevisível antes da criação, mas pode ser armazenado junto do hash. O formato retornado por argon2-cffi já inclui o salt e os parâmetros.
Argon2id dispensa autenticação multifator?
Não. Argon2id protege melhor o material armazenado contra quebra offline. MFA reduz outros riscos, como senha reutilizada, phishing ou credencial já conhecida pelo invasor.
Devo fixar manualmente os parâmetros do exemplo?
Use parâmetros mantidos pela biblioteca como ponto de partida e faça benchmark no seu ambiente.
Valores copiados podem ser leves demais para um servidor potente ou pesados demais para uma infraestrutura pequena. Registre a decisão e reavalie periodicamente.
Quando ainda faz sentido usar bcrypt?
Principalmente ao manter ou migrar uma aplicação que já o utiliza. Para projeto novo, Argon2id é a preferência atual do OWASP. Se bcrypt permanecer, trate corretamente o limite de 72 bytes e calibre o fator de custo.
Conclusão
A forma segura de implementar “criptografia de senhas em Python” é não guardar algo que possa ser descriptografado.
Use hashing específico para senhas, preferencialmente Argon2id em projetos novos, deixe argon2-cffi gerar o salt, persista a string codificada completa e verifique com a API da biblioteca.
Depois, complete a proteção com parâmetros medidos, rehash após login válido, migração planejada, limitação de tentativas, MFA, controle de acesso e monitoramento.
Segurança de senha não é uma linha isolada de código: é um fluxo operacional que precisa continuar correto mesmo quando banco, backup ou aplicação são pressionados por um incidente.