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.

8 min de leitura Equipe Python Brasil

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:

  1. hashlib trabalha com bytes, não com str. Por isso o .encode("utf-8").
  2. 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:

NecessidadeFerramenta
Token de recuperação / API key aleatóriasecrets
Fingerprint de arquivo ou payloadhashlib (SHA-256 / BLAKE2)
Webhook autenticadohmac + hashlib
Senha de usuárioArgon2id / scrypt / bcrypt
Identificador público de recursouuid

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:

  1. baixe o CSV e o manifesto;
  2. recalcule o SHA-256;
  3. compare com compare_digest;
  4. 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

  1. Hashear str sem encoding explícito — quebra entre sistemas.
  2. Usar MD5 “porque é mais curto” — curto não é critério de segurança.
  3. Comparar digests com == em fluxos sensíveis — prefira compare_digest.
  4. Recarregar JSON antes de validar HMAC — a assinatura deve cobrir os bytes originais.
  5. Usar hash rápido para senha de usuário — escolha algoritmo de senha de propósito.
  6. 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.
  7. 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:

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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português