Pular para conteúdo

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!")
$ pip install bcrypt $ python -c "import bcrypt; print(bcrypt.hashpw(b'senha123', bcrypt.gensalt()))" $ b'$2b$12$EixZaYVK1fsbw1Zfbx...'

📏 Boas Práticas Adicionais

  1. Nunca limite o tamanho máximo da senha artificialmente (limites curtos como "máximo 8 caracteres" reduzem segurança).
  2. Não force troca periódica sem motivo (pesquisas mostram que isso leva a senhas mais fracas e previsíveis, como "Senha1", "Senha2"...).
  3. Sempre use HTTPS no formulário de login (senão a senha viaja em texto claro na rede).
  4. Implemente rate limiting para bloquear tentativas de força bruta.

📝 Exercícios de Fixação

  1. Básico: Por que armazenar senhas em texto puro é uma péssima prática?
  2. Básico: O que é um "salt" e qual problema ele resolve?
  3. Intermediário: Por que SHA-256 puro não é adequado para hash de senhas, mesmo sendo um algoritmo seguro?
  4. Intermediário: O que significa o "fator de custo" do bcrypt e por que ele pode ser aumentado ao longo dos anos?
  5. 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.

  1. Crie uma função cadastrar(usuario, senha) que gera o hash com bcrypt e "salva" (pode ser em um dicionário em memória).
  2. Crie uma função login(usuario, senha) que verifica a senha digitada contra o hash salvo.
  3. Teste com senha correta e senha incorreta.
  4. Bônus: adicione um contador de tentativas falhas e bloqueie o login após 5 tentativas (rate limiting básico).

Ir para Aula 14