Aula 13 – Gestão e Armazenamento Seguro de Senhas 🗝️
"Nunca guarde a senha do usuário." Essa frase resume uma das regras mais importantes de segurança — e nesta aula você vai entender exatamente o porquê e como fazer certo.
❌ O que NUNCA fazer
# ❌ Senha em texto puro no banco de dados
usuario.senha = "senha123"
# ❌ Hash simples sem sal (SHA-256 puro)
usuario.senha = hashlib.sha256("senha123".encode()).hexdigest()
Por que hash simples não basta?
Hashes como SHA-256 são rápidos demais — um atacante com hardware moderno (GPU) consegue testar bilhões de senhas por segundo contra um hash roubado, usando rainbow tables (tabelas pré-computadas) ou força bruta.
🧂 Sal (Salt): a Primeira Defesa
Um salt é um valor aleatório único, gerado para cada senha, que é combinado com a senha antes de aplicar o hash.
graph LR
A["senha123"] --> C["+ salt aleatório (ex: 8f3a...)"]
C --> H["Função de Hash"]
H --> R["Hash único, mesmo que outro usuário tenha a mesma senha"] Sem salt, duas contas com a mesma senha ("123456", por exemplo) teriam o mesmo hash — permitindo que um atacante quebre todas de uma vez com uma única rainbow table.
🐌 Funções de Hash Lentas de Propósito
A verdadeira solução não é só adicionar sal — é usar algoritmos deliberadamente lentos, projetados especificamente para senhas:
| Algoritmo | Características |
|---|---|
| bcrypt | Padrão de mercado, salt embutido automaticamente, "fator de custo" ajustável |
| scrypt | Também exige muita memória, dificultando ataques com hardware especializado (ASIC) |
| Argon2 | Vencedor da Password Hashing Competition (2015), recomendação atual mais forte |
Por que 'lento' é bom aqui?
Para o usuário, uma senha demorar 200ms para verificar no login é imperceptível. Para um atacante tentando testar bilhões de combinações, esses 200ms por tentativa tornam o ataque impraticável.
💻 Hash de Senha com bcrypt em Python
import bcrypt
senha = b"senha123"
# Cadastro: gera salt automaticamente e cifra
hash_senha = bcrypt.hashpw(senha, bcrypt.gensalt())
print(hash_senha)
# b'$2b$12$KIXQ...'
# Login: verifica se a senha digitada bate com o hash salvo
senha_digitada = b"senha123"
if bcrypt.checkpw(senha_digitada, hash_senha):
print("✅ Senha correta!")
else:
print("❌ Senha incorreta!")
📏 Boas Práticas Adicionais
- Nunca limite o tamanho máximo da senha artificialmente (limites curtos como "máximo 8 caracteres" reduzem segurança).
- Não force troca periódica sem motivo (pesquisas mostram que isso leva a senhas mais fracas e previsíveis, como "Senha1", "Senha2"...).
- Sempre use HTTPS no formulário de login (senão a senha viaja em texto claro na rede).
- Implemente rate limiting para bloquear tentativas de força bruta.
📝 Exercícios de Fixação
- Básico: Por que armazenar senhas em texto puro é uma péssima prática?
- Básico: O que é um "salt" e qual problema ele resolve?
- Intermediário: Por que SHA-256 puro não é adequado para hash de senhas, mesmo sendo um algoritmo seguro?
- Intermediário: O que significa o "fator de custo" do bcrypt e por que ele pode ser aumentado ao longo dos anos?
- Desafio: Pesquise o que é "credential stuffing" e por que reutilizar senhas entre sites é especialmente perigoso frente a esse ataque.
🚀 Mini-Projeto: Sistema de Cadastro e Login
Objetivo: Implementar um cadastro/login simples com hash de senha seguro.
- Crie uma função
cadastrar(usuario, senha)que gera o hash com bcrypt e "salva" (pode ser em um dicionário em memória). - Crie uma função
login(usuario, senha)que verifica a senha digitada contra o hash salvo. - Teste com senha correta e senha incorreta.
- Bônus: adicione um contador de tentativas falhas e bloqueie o login após 5 tentativas (rate limiting básico).