Especialização em Backend com Python & FastAPI • Demonstrações Práticas Compiláveis


💻 Exemplo 1 — CryptContext com Bcrypt: Hash, Verificação e Validação de Complexidade

import re

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto", bcrypt__rounds=12)


def hash_senha(senha_plana: str) -> str:
    return pwd_context.hash(senha_plana)


def verificar_senha(senha_plana: str, hash_armazenado: str) -> bool:
    return pwd_context.verify(senha_plana, hash_armazenado)


def validar_complexidade(senha: str) -> tuple[bool, list[str]]:
    erros: list[str] = []
    if len(senha) < 8:
        erros.append("mínimo de 8 caracteres")
    if not re.search(r"[A-Z]", senha):
        erros.append("ao menos 1 letra maiúscula")
    if not re.search(r"[a-z]", senha):
        erros.append("ao menos 1 letra minúscula")
    if not re.search(r"[0-9]", senha):
        erros.append("ao menos 1 dígito")
    if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", senha):
        erros.append("ao menos 1 caractere especial")
    return (len(erros) == 0, erros)


valido, problemas = validar_complexidade("Tech@2026")
print("Senha válida:", valido, problemas)

hash_gerado = hash_senha("Tech@2026")
print("Hash Bcrypt:", hash_gerado[:29], "...")
print("Verificação correta:", verificar_senha("Tech@2026", hash_gerado))
print("Verificação incorreta:", verificar_senha("SenhaErrada", hash_gerado))

💻 Exemplo 2 — Autenticação Timing-Safe contra Enumeração de Usuários

from dataclasses import dataclass

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Hash "isca" pré-computado uma única vez no startup da aplicação, usado
# quando o usuário não existe, para que o tempo de resposta seja idêntico.
HASH_DUMMY = pwd_context.hash("senha-que-nunca-sera-usada-em-producao")


@dataclass
class Usuario:
    email: str
    senha_hash: str


BASE_USUARIOS = {
    "dev@empresa.com": Usuario(email="dev@empresa.com", senha_hash=pwd_context.hash("SenhaForte#1")),
}


def autenticar(email: str, senha_tentativa: str) -> bool:
    usuario = BASE_USUARIOS.get(email)
    hash_alvo = usuario.senha_hash if usuario else HASH_DUMMY

    # Sempre executa o verify(), exista ou não o usuário: o custo do bcrypt
    # é pago igualmente nos dois casos, eliminando a diferença de tempo
    # que permitiria a um atacante inferir quais e-mails estão cadastrados.
    senha_confere = pwd_context.verify(senha_tentativa, hash_alvo)
    return usuario is not None and senha_confere


print("Usuário existe, senha certa:", autenticar("dev@empresa.com", "SenhaForte#1"))
print("Usuário existe, senha errada:", autenticar("dev@empresa.com", "outra"))
print("Usuário inexistente:", autenticar("fantasma@empresa.com", "qualquer"))

💻 Exemplo 3 — Migração Transparente de Hashes Legados com needs_rehash

from passlib.context import CryptContext

# "bcrypt" é o esquema ativo; "md5_crypt" só é aceito para leitura de
# hashes legados e é automaticamente marcado como depreciado.
pwd_context = CryptContext(
    schemes=["bcrypt", "md5_crypt"],
    deprecated=["md5_crypt"],
    bcrypt__rounds=12,
)


class RepositorioUsuariosFake:
    def __init__(self) -> None:
        # Simula um usuário legado migrado de um sistema antigo em MD5.
        self._hash_legado = pwd_context.hash("senha123", scheme="md5_crypt")

    def buscar_hash(self) -> str:
        return self._hash_legado

    def atualizar_hash(self, novo_hash: str) -> None:
        self._hash_legado = novo_hash
        print("Hash do usuário atualizado no banco para o esquema atual.")


def login(senha_tentativa: str, repo: RepositorioUsuariosFake) -> str:
    hash_atual = repo.buscar_hash()

    if not pwd_context.verify(senha_tentativa, hash_atual):
        raise ValueError("Credenciais inválidas")

    if pwd_context.needs_rehash(hash_atual):
        novo_hash = pwd_context.hash(senha_tentativa)
        repo.atualizar_hash(novo_hash)

    return "autenticado"


repositorio = RepositorioUsuariosFake()
print("Hash antes do login:", repositorio.buscar_hash()[:10], "...")
print("Resultado do login:", login("senha123", repositorio))
print("Hash depois do login:", repositorio.buscar_hash()[:10], "...")