hashlib em Python: checksums, integridade e hashes seguros
Aprenda hashlib em Python para calcular SHA-256, verificar integridade de arquivos, assinar mensagens com HMAC e hash de senhas sem confiar em MD5.
O módulo hashlib é a ferramenta da biblioteca padrão para calcular checksums, fingerprints e resumos criptográficos em Python. Com ele você verifica se um arquivo baixado bate com o hash publicado, detecta alteração em payloads de webhook, gera chaves estáveis a partir de conteúdo e evita confiar cegamente em MD5.
A recomendação direta é: use hashlib.sha256() ou hashlib.blake2b() para integridade; alimente dados grandes com update() em blocos ou file_digest(); compare digests com hmac.compare_digest(); e nunca use hash rápido puro para senhas de usuário. Para tokens aleatórios, continue em secrets; para IDs públicos, use uuid.
Neste guia, você vai calcular SHA-256 de strings e arquivos, montar verificação de download, assinar mensagens com HMAC, entender o papel do salt e separar três problemas diferentes: integridade, autenticação de mensagem e armazenamento de senha.
O que um hash resolve — e o que ele não resolve
Uma função de hash mapeia dados de tamanho arbitrário para um resumo de tamanho fixo. Em SHA-256, o resumo tem 32 bytes (64 caracteres hexadecimais). A mesma entrada sempre produz a mesma saída. Uma mudança mínima no conteúdo muda o digest de forma imprevisível.
Isso é útil quando você precisa responder:
- este arquivo ainda é o mesmo que eu baixei ontem?
- o corpo do webhook chegou intacto?
- dois documentos são byte a byte iguais sem compará-los inteiros na rede?
- qual fingerprint curto eu gravo no banco para detectar reenvio?
Hash não é criptografia reversível. Você não “desfaz” um SHA-256 para obter o texto original. Hash também não autentica sozinho a origem da mensagem: se o atacante puder alterar o conteúdo e o hash ao mesmo tempo, a verificação passa. Para autenticar origem e integridade juntos, use HMAC com uma chave secreta, ou assinatura assimétrica.
Como calcular SHA-256 de um texto
O caminho mais comum:
import hashlib
mensagem = "pedido-1001|aprovado"
digest = hashlib.sha256(mensagem.encode("utf-8")).hexdigest()
print(digest)
Saída típica (o valor muda se o texto mudar):
a3f1... # 64 caracteres hexadecimais
Dois detalhes importantes:
hashlibtrabalha com bytes, não comstr. Por isso o.encode("utf-8").hexdigest()devolve texto legível;digest()devolve os 32 bytes brutos, melhores para armazenamento binário ou comparação interna.
Evite confiar em encoding implícito. Em sistemas brasileiros, nomes de arquivo, CPF formatado e textos com acentos precisam de uma política explícita de normalização antes do hash. Se um serviço hasheia "José" em UTF-8 e outro em Latin-1, os digests nunca batem.
Hash incremental e arquivos grandes
Para arquivos, logs e streams, não carregue tudo na memória:
from pathlib import Path
import hashlib
def sha256_arquivo(caminho: Path, bloco: int = 1024 * 1024) -> str:
h = hashlib.sha256()
with caminho.open("rb") as f:
while True:
pedaco = f.read(bloco)
if not pedaco:
break
h.update(pedaco)
return h.hexdigest()
A partir do Python 3.11 existe um atalho oficial:
from pathlib import Path
import hashlib
with Path("relatorio.pdf").open("rb") as f:
digest = hashlib.file_digest(f, "sha256").hexdigest()
file_digest() reduz boilerplate e deixa a intenção clara. Em workers que processam uploads de NFe, planilhas ou backups, essa leitura em blocos evita picos de memória e timeouts.
Verificar integridade de um download
Um padrão clássico em repositórios e pacotes internos:
from pathlib import Path
import hashlib
import hmac
def arquivo_integro(caminho: Path, esperado_hex: str) -> bool:
with caminho.open("rb") as f:
obtido = hashlib.file_digest(f, "sha256").hexdigest()
return hmac.compare_digest(obtido.lower(), esperado_hex.lower())
Use hmac.compare_digest em vez de == quando o valor comparado for segredo ou quando a verificação fizer parte de um fluxo sensível. A comparação em tempo constante reduz uma classe de ataques de timing.
Em automação de CI, grave o hash esperado no manifesto do artefato e falhe o pipeline se o binário baixado divergir. Isso é especialmente útil em ambientes corporativos brasileiros que espelham pacotes internos ou imagens base.
MD5 e SHA-1: legados, não padrão de segurança
Ainda existem sistemas que pedem MD5. Para compatibilidade com legado, o Python continua expondo o algoritmo:
import hashlib
print(hashlib.md5(b"legado").hexdigest())
Trate MD5 e SHA-1 como compatibilidade, não como proteção. Existem colisões práticas: um atacante pode produzir dois arquivos diferentes com o mesmo MD5. Se o objetivo for segurança ou auditoria moderna, migre para SHA-256, SHA-512 ou BLAKE2.
Quando um parceiro ainda exige MD5 no layout de arquivo, calcule o MD5 exigido e um SHA-256 interno para a sua trilha de auditoria.
BLAKE2 e escolha de algoritmo
Para fingerprints locais de alto desempenho, BLAKE2 costuma ser excelente:
import hashlib
print(hashlib.blake2b(b"conteudo", digest_size=32).hexdigest())
Regras práticas:
- SHA-256: padrão intercambiável, amplo suporte em APIs, bancos e documentação.
- SHA-512: útil quando o ambiente já padroniza 512 bits.
- BLAKE2b / BLAKE2s: ótimo desempenho em software; ótimo para fingerprints internos.
- SHA-3: disponível, mas menos comum em integrações legadas.
- MD5 / SHA-1: só para interoperar com sistemas antigos.
Liste o que a instalação oferece com hashlib.algorithms_guaranteed e hashlib.algorithms_available se o código rodar em múltiplas plataformas.
HMAC: integridade com chave secreta
Quando a pergunta muda de “o conteúdo mudou?” para “o conteúdo mudou e veio de quem conhece a chave?”, use HMAC:
import hashlib
import hmac
chave = b"segredo-do-webhook"
corpo = b'{"evento":"pagamento.aprovado","id":"px_123"}'
assinatura = hmac.new(chave, corpo, hashlib.sha256).hexdigest()
print(assinatura)
Na validação:
import hmac
import hashlib
def webhook_valido(chave: bytes, corpo: bytes, assinatura_recebida: str) -> bool:
esperada = hmac.new(chave, corpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, assinatura_recebida)
Esse padrão aparece em webhooks de pagamento, filas internas e callbacks de CRM. A chave fica em variável de ambiente ou cofre; o corpo assinado é o payload bruto recebido, sem re-serializar o JSON de forma diferente da origem.
Para geração e rotação de chaves aleatórias, combine com o módulo secrets. Para configuração tipada de ambiente, veja também python-dotenv e variáveis de ambiente.
Salt, pepper e o erro de hashear senha “no braço”
Um exemplo incompleto e inadequado para senhas reais:
import hashlib
def hash_fraco(senha: str) -> str:
return hashlib.sha256(senha.encode("utf-8")).hexdigest()
Problemas:
- o mesmo senha sempre gera o mesmo hash (fácil de correlacionar);
- SHA-256 é rápido demais para brute force com GPU;
- não há fator de custo configurável.
Mesmo com salt simples, hash rápido puro continua frágil para senhas de usuário. A stdlib oferece PBKDF2:
import hashlib
import os
import secrets
def derivar_chave(senha: str, salt: bytes | None = None) -> tuple[bytes, bytes]:
if salt is None:
salt = secrets.token_bytes(16)
chave = hashlib.pbkdf2_hmac(
"sha256",
senha.encode("utf-8"),
salt,
iterations=600_000,
)
return salt, chave
PBKDF2 com muitas iterações é aceitável em alguns contextos de derivação de chave, mas aplicações novas de autenticação de usuário devem preferir Argon2id, scrypt ou bcrypt via bibliotecas mantidas. Guarde salt por usuário, parâmetros do algoritmo e nunca o segredo em texto puro.
Resumo da separação:
| Necessidade | Ferramenta |
|---|---|
| Token de recuperação / API key aleatória | secrets |
| Fingerprint de arquivo ou payload | hashlib (SHA-256 / BLAKE2) |
| Webhook autenticado | hmac + hashlib |
| Senha de usuário | Argon2id / scrypt / bcrypt |
| Identificador público de recurso | uuid |
Caso prático: manifesto de artefatos em pipeline
Imagine um job que gera um CSV de conciliação e publica o arquivo em object storage:
from pathlib import Path
import hashlib
import json
def publicar_manifesto(arquivo: Path) -> dict:
with arquivo.open("rb") as f:
digest = hashlib.file_digest(f, "sha256").hexdigest()
manifesto = {
"nome": arquivo.name,
"algoritmo": "sha256",
"digest": digest,
"tamanho": arquivo.stat().st_size,
}
Path(f"{arquivo}.sha256.json").write_text(
json.dumps(manifesto, ensure_ascii=False, indent=2),
encoding="utf-8",
)
return manifesto
No consumidor:
- baixe o CSV e o manifesto;
- recalcule o SHA-256;
- compare com
compare_digest; - só então carregue o arquivo no pandas, DuckDB ou no banco.
Esse fluxo reduz o risco de processar um artefato truncado por falha de rede ou sobrescrito por deploy paralelo. Em times de dados e financeiro, a falha “silenciosa” de arquivo incompleto costuma ser mais cara do que o retry explícito.
Erros comuns com hashlib
- Hashear
strsem encoding explícito — quebra entre sistemas. - Usar MD5 “porque é mais curto” — curto não é critério de segurança.
- Comparar digests com
==em fluxos sensíveis — prefiracompare_digest. - Recarregar JSON antes de validar HMAC — a assinatura deve cobrir os bytes originais.
- Usar hash rápido para senha de usuário — escolha algoritmo de senha de propósito.
- Logar digest de tokens de sessão como se fossem públicos — fingerprint de segredo ainda pode vazar informação em alguns desenhos; minimize logs.
- Confiar no nome do arquivo em vez do conteúdo — o hash fala do conteúdo; o nome não.
Testes determinísticos
Hashes são determinísticos, o que facilita testes:
import hashlib
def fingerprint_pedido(payload: str) -> str:
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def test_fingerprint_estavel():
assert fingerprint_pedido("a") == fingerprint_pedido("a")
assert fingerprint_pedido("a") != fingerprint_pedido("b")
Para HMAC, injete a chave no teste. Para arquivos, use tmp_path do pytest e um conteúdo minúsculo. Evite depender de arquivos grandes do repositório só para validar o algoritmo.
Quando hashlib não é a ferramenta certa
- Criptografar dados para depois recuperar: use bibliotecas de criptografia autenticada, não hash.
- Gerar ID público de pedido: prefira
uuid. - Gerar segredo adivinhável apenas por quem o possui: use
secrets. - Assinatura com par de chaves pública/privada: use ecossistema de assinatura digital, não só HMAC simétrico.
- Deduplicação semântica de texto (“frases parecidas”): hash criptográfico exige igualdade exata de bytes; similaridade pede outra técnica.
Conclusão e próximos passos
hashlib é pequeno, estável e suficiente para a maior parte das necessidades de integridade e fingerprint em Python. Padronize SHA-256 ou BLAKE2, leia arquivos em blocos, valide webhooks com HMAC e reserve algoritmos de senha modernos para credenciais de usuário.
Para continuar no mesmo eixo prático:
- secrets em Python: tokens e senhas aleatórias
- uuid em Python: identificadores únicos
- Segurança em aplicações Python
- Variáveis de ambiente com python-dotenv
- Subprocess e comandos externos
Se o seu time publica artefatos, processa webhooks ou confere downloads em automação, um helper de SHA-256 com file_digest e compare_digest costuma eliminar uma classe inteira de incidentes silenciosos.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português