HMAC em Python — hmac.new() SHA-256 com exemplos de código
Use o Gerador HMAC gratuito diretamente no seu navegador — sem instalação.
Experimentar Gerador HMAC online →Todo callback de webhook, toda requisição de API assinada, toda notificação de evento do Stripe ou GitHub usa uma assinatura HMAC para provar que o payload não foi adulterado. O módulo hmac do Python trata HMAC-SHA256 em Python com uma única chamada de função: hmac.new(key, msg, hashlib.sha256). Sem pip install, sem extensão C, sem dependência de terceiros. Para verificações rápidas de assinatura sem escrever código, o HMAC Generator online dá o resultado instantaneamente. Este guia abrange hmac.new(), hmac.digest(), hmac.compare_digest(), codificação Base64, verificação de webhooks, assinatura de requisições de API e todos os algoritmos de hash, de SHA-1 a SHA-512. Todos os exemplos têm como alvo o Python 3.7+.
- ✓hmac.new(key, msg, hashlib.sha256) é o ponto de entrada padrão — key e msg devem ser bytes, e digestmod é obrigatório desde o Python 3.4.
- ✓hmac.digest(key, msg, "sha256") é uma alternativa one-shot mais rápida, adicionada no Python 3.7 — retorna bytes brutos sem objeto intermediário.
- ✓Sempre verifique assinaturas com hmac.compare_digest() para evitar ataques de temporização — nunca use == para comparação de HMAC.
- ✓Codifique em Base64 a saída bruta de .digest() para cabeçalhos HTTP e assinaturas de webhook: base64.b64encode(h.digest()).
- ✓O módulo hmac aceita qualquer algoritmo do hashlib: sha1, sha256, sha384, sha512, md5, blake2b.
O que é HMAC?
HMAC (Hash-based Message Authentication Code) é uma construção definida na RFC 2104 que combina uma chave secreta com uma função de hash para produzir uma tag de autenticação de tamanho fixo. Ao contrário de um hash simples (que qualquer pessoa pode calcular), um HMAC requer conhecimento da chave secreta. Isso significa que você pode usá-lo para verificar tanto a integridade quanto a autenticidade de uma mensagem. Se mesmo um único byte da mensagem ou da chave mudar, a saída é completamente diferente. A construção funciona fazendo o hash da chave com XOR aplicado a duas constantes de padding diferentes (ipad e opad), envolvendo a mensagem entre duas operações de hash. O módulo hmac do Python implementa essa RFC diretamente.
# Hash SHA-256 simples — sem chave secreta, qualquer pessoa pode calcular hashlib.sha256(b"payment:9950:USD").hexdigest() # "7a3b1c..." (determinístico, público)
# HMAC-SHA256 — requer a chave secreta para produzir hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest() # "e4f2a8..." (apenas o detentor da chave pode calcular)
hmac.new() — O Ponto de Entrada da Biblioteca Padrão
O módulo hmac faz parte da biblioteca padrão do Python. Dois imports e você está pronto: import hmac, hashlib. As três funções principais são hmac.new() (cria um objeto HMAC), hmac.digest() (one-shot, Python 3.7+) e hmac.compare_digest() (comparação em tempo constante). Nenhum pip install necessário.
hmac.new(key, msg, digestmod) recebe três argumentos. Tanto key quanto msg devem ser objetos bytes-like ( bytes, bytearray ou memoryview). O argumento digestmod é obrigatório desde o Python 3.4 e aceita qualquer construtor do hashlib (como hashlib.sha256) ou um nome em string como "sha256".
import hmac
import hashlib
key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'
# Cria o objeto HMAC e obtém a assinatura em hex
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"O objeto HMAC expõe dois métodos de saída. .digest() retorna bytes brutos (32 bytes para SHA-256, 64 para SHA-512). .hexdigest() retorna uma string hex em letras minúsculas. A string hex é um str Python simples — sem necessidade de etapa de decodificação.
import hmac import hashlib key = b"service_auth_key" msg = b"GET /api/v2/orders 2026-03-28T14:30:00Z" h = hmac.new(key, msg, hashlib.sha256) raw_bytes = h.digest() print(type(raw_bytes), len(raw_bytes)) # <class 'bytes'> 32 hex_string = h.hexdigest() print(type(hex_string), len(hex_string)) # <class 'str'> 64 # Representam os mesmos dados — hex é apenas uma codificação em string dos bytes assert raw_bytes.hex() == hex_string
Se sua chave ou mensagem for uma string Python, chame .encode() para convertê-la em bytes antes de passá-la ao hmac.new(). Isso pega quase todo mundo na primeira vez — strings do Python 3 são Unicode, não bytes, e o módulo hmac as recusa.
import hmac
import hashlib
# Chave e mensagem em string — .encode() converte para bytes UTF-8
api_key = "sk_live_9f3a2b7c4d8e"
request_body = '{"customer_id":"cust_4421","plan":"enterprise"}'
signature = hmac.new(
api_key.encode(),
request_body.encode(),
hashlib.sha256
).hexdigest()
print(signature)
# "3a9f1b..." — saída consistente em string hexdigestmod não tem valor padrão desde o Python 3.4. Chamar hmac.new(key, msg) sem ele levanta TypeError. Antes do 3.4, o padrão era MD5, e foi exatamente por isso que os mantenedores do Python removeram o padrão — forçando uma escolha explícita e segura.HMAC-SHA256 Base64, SHA-1, SHA-512 e MD5
A função hmac.new() funciona com qualquer algoritmo de hash disponível no hashlib. A maioria dos provedores de webhook e gateways de API usa HMAC-SHA256, mas você vai encontrar SHA-1 no OAuth 1.0a, SHA-512 em protocolos que o exigem, e MD5 em sistemas legados que ainda não foram atualizados.
HMAC-SHA256 com Saída em Base64
Muitos provedores de webhook enviam a assinatura como uma string codificada em Base64 em um cabeçalho HTTP. Para produzir o mesmo formato, passe os bytes brutos de .digest() para base64.b64encode().
import hmac
import hashlib
import base64
key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'
# Digest bruto → Base64 (comum para cabeçalhos Authorization e assinaturas de webhook)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")
print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="
# Este é o valor que você compararia contra o cabeçalho X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="HMAC-SHA1 — Compatibilidade com Protocolos Legados
SHA-1 é considerado fraco para novos projetos, mas HMAC-SHA1 ainda é exigido pelo OAuth 1.0a e algumas implementações mais antigas de webhook. O código é idêntico — basta trocar o algoritmo.
import hmac
import hashlib
consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"
# OAuth 1.0a usa consumer_secret&token_secret como chave de assinatura
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"
sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()
import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..." — codifique em URL para o cabeçalho AuthorizationHMAC-SHA512 — Saída Maior
import hmac
import hashlib
key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'
h = hmac.new(key, msg, hashlib.sha512)
print(len(h.digest())) # 64 bytes (512 bits)
print(len(h.hexdigest())) # 128 caracteres hex
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."HMAC-MD5 — Somente para Legado
import hmac import hashlib # MD5 é criptograficamente quebrado — use apenas para compatibilidade com protocolos legados key = b"legacy_api_key" msg = b"action=charge&amount=1500&merchant=store_42" sig = hmac.new(key, msg, hashlib.md5).hexdigest() print(sig) # string hex de 32 caracteres # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Referência de Parâmetros do hmac.new()
A assinatura do construtor é hmac.new(key, msg=None, digestmod). As três funções do módulo compartilham o mesmo padrão para argumentos de chave e algoritmo.
Construtor hmac.new()
hmac.digest() one-shot (Python 3.7+)
O parâmetro digestmod aceita um callable (como hashlib.sha256) ou um nome em string (como "sha256"). A forma com callable é preferida porque é validada no momento do import — um erro de digitação na forma string só falha em tempo de execução.
hmac.digest() — HMAC One-Shot Rápido (Python 3.7+)
O Python 3.7 adicionou hmac.digest(key, msg, digest) como uma função de nível de módulo. Ela calcula o HMAC em uma única chamada sem criar um objeto HMAC intermediário. O valor retornado são bytes brutos (equivalente a chamar .digest() no objeto). Essa função usa uma implementação C otimizada no CPython e evita a sobrecarga de alocação de objeto, tornando-a visivelmente mais rápida em loops densos.
import hmac
import hashlib
key = b"batch_signing_key_2026"
messages = [
b'{"order_id":"ord_001","total":4500}',
b'{"order_id":"ord_002","total":8900}',
b'{"order_id":"ord_003","total":2200}',
]
# Digest one-shot — sem objeto HMAC intermediário
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]
# Converte para hex para exibição
for msg, sig in zip(messages, signatures):
print(f"{msg[:30]}... -> {sig.hex()[:24]}...")A limitação: hmac.digest() retorna apenas bytes brutos. Se você precisar da string hex diretamente, ainda é necessário usar hmac.new() pelo seu método .hexdigest(), ou encadear .hex() no resultado em bytes.
hmac.digest() não suporta chamadas incrementais a .update(). Se você estiver lendo um arquivo grande em partes e precisar calcular o HMAC do conteúdo, use hmac.new() e chame .update(chunk) em um loop.Verificar Assinatura HMAC de Webhook e Resposta de API
O uso mais comum de HMAC em Python é a verificação de assinaturas de webhook. Todo grande provedor (Stripe, GitHub, Shopify, Twilio) assina payloads com HMAC-SHA256 e envia a assinatura em um cabeçalho. O padrão é sempre o mesmo: recalcule o HMAC sobre o corpo bruto da requisição, depois compare com hmac.compare_digest().
Verificação de Assinatura de Webhook
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"
@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
# Obtém o corpo bruto — deve corresponder exatamente ao que foi assinado
raw_body = request.get_data()
# Obtém a assinatura do cabeçalho
received_sig = request.headers.get("X-Signature-256", "")
# Recalcula o HMAC sobre o corpo bruto
expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
# Comparação em tempo constante — evita ataques de temporização
if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
abort(403, "Assinatura inválida")
# Assinatura verificada — processa o evento
event = request.get_json()
print(f"Evento verificado: {event['type']} para {event['data']['amount']}")
return "", 200A função hmac.compare_digest() compara duas strings ou sequências de bytes em tempo constante. Uma comparação regular com == faz curto-circuito no primeiro byte divergente. Um atacante pode medir o tempo de resposta em muitas requisições e reconstruir gradualmente a assinatura esperada byte por byte. A comparação em tempo constante elimina esse canal lateral.
Verificação de Webhook do GitHub
O formato de webhook do GitHub ilustra o padrão completo. Ele envia um cabeçalho X-Hub-Signature-256 contendo sha256= seguido do HMAC-SHA256 em hex do corpo bruto da requisição, assinado com o segredo de webhook configurado nas configurações do seu repositório. A diferença principal em relação à verificação genérica de webhook é que você deve remover o prefixo sha256= antes de comparar, e deve ler os bytes brutos do corpo da requisição — fazer o parse do JSON antes disso muda a representação em bytes e quebra a verificação.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"
@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
# GitHub envia: X-Hub-Signature-256: sha256=<hex_digest>
sig_header = request.headers.get("X-Hub-Signature-256", "")
if not sig_header.startswith("sha256="):
abort(403, "Cabeçalho de assinatura ausente ou malformado")
received_hex = sig_header[len("sha256="):]
raw_body = request.get_data() # bytes brutos — não faça parse do JSON antes disso
expected_hex = hmac.new(
GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_hex, received_hex):
abort(403, "Assinatura não corresponde — o payload pode ter sido adulterado")
event_type = request.headers.get("X-GitHub-Event", "unknown")
payload = request.get_json()
print(f"Evento GitHub {event_type} verificado: {payload.get('action', '')}")
return "", 200O mesmo padrão se aplica ao Shopify (X-Shopify-Hmac-SHA256) e ao Twilio (X-Twilio-Signature), com a única diferença sendo o nome do cabeçalho e se a assinatura está em hex ou codificada em Base64. Sempre verifique a documentação do provedor para confirmar o formato de codificação — misturar hex e Base64 é a causa mais comum de erros de assinatura não correspondente.
Autenticação de Requisições de API com HMAC
Algumas APIs exigem que o cliente assine cada requisição com HMAC. A string assinada geralmente inclui o método HTTP, o caminho, o timestamp e o corpo da requisição. Aqui está um padrão que uso para autenticação interna entre serviços.
import hmac
import hashlib
import time
import json
def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
"""Gera assinatura HMAC-SHA256 para uma requisição de API."""
timestamp = str(int(time.time()))
# Constrói a string de assinatura — método + caminho + timestamp + corpo
signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(
secret,
signing_string.encode(),
hashlib.sha256
).hexdigest()
return {
"X-Timestamp": timestamp,
"X-Signature": signature,
}
# Uso
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})
headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}Assinando Requisições HTTP com a Biblioteca requests
import hmac
import hashlib
import time
import json
import requests
API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"
def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":")) # JSON compacto, determinístico
timestamp = str(int(time.time()))
signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Signature": f"hmac-sha256={signature}",
}
try:
return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
except requests.RequestException as e:
raise RuntimeError(f"Requisição assinada falhou: {e}") from e
# Envia uma requisição POST assinada
resp = make_signed_request("POST", "/api/v2/invoices", {
"customer_id": "cust_4421",
"line_items": [
{"description": "Plano Pro - Março 2026", "amount": 4900},
{"description": "Assentos extras (3)", "amount": 2100},
],
})
print(resp.status_code, resp.json())Observação rápida: use separators=(",", ":") ao serializar o corpo para assinatura. O json.dumps() padrão adiciona espaços após os separadores, o que muda a representação em bytes e quebra a verificação de assinatura se o servidor serializar de forma diferente. JSON compacto dá uma forma canônica.
Geração de HMAC pela Linha de Comando
Às vezes você precisa calcular um HMAC sem escrever um script. O flag -c do Python e o openssl resolvem isso pelo terminal.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # saída: string hex de 64 caracteres
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret" # SHA2-256(stdin)= 7d11... # Passa um arquivo pelo HMAC do openssl openssl dgst -sha256 -hmac "my_secret" < payload.json
# Armazena a chave em variável de ambiente para evitar exposição no histórico do shell
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"echo -n é essencial — sem ele, o echo acrescenta um caractere de nova linha à mensagem, o que altera a saída do HMAC. Essa é a causa mais comum de assinatura não correspondente ao depurar pelo terminal.Alternativa de Alto Desempenho — Biblioteca cryptography
Para a maioria das aplicações, o módulo hmac padrão é rápido o suficiente. Se você já usa a biblioteca cryptography para TLS ou manipulação de certificados, ela também oferece HMAC com OpenSSL por baixo. A principal diferença prática em relação à stdlib é a API .verify() baseada em exceção descrita a seguir — ela levanta uma exceção em caso de divergência em vez de retornar um booleano que você pode esquecer de verificar.
pip install cryptography
from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac
key = b"webhook_signing_key_2026"
message = b'{"event":"subscription.renewed","plan":"enterprise"}'
h = crypto_hmac.HMAC(key, hashes.SHA256())
h.update(message)
signature = h.finalize() # bytes brutos
print(signature.hex())
# "9c4e2a..."
# Modo de verificação — levanta InvalidSignature em caso de divergência
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature) # levanta cryptography.exceptions.InvalidSignature se erradoO método .verify() da biblioteca cryptography é particularmente útil: ele levanta uma exceção em caso de divergência em vez de retornar um booleano. Isso torna mais difícil ignorar acidentalmente uma falha de verificação. O hmac.compare_digest() da biblioteca padrão retorna True/ False, que pode ser silenciosamente ignorado se o desenvolvedor esquecer de verificar o valor de retorno.
Saída no Terminal com Realce de Sintaxe
Se você está depurando assinaturas HMAC no terminal e quer saída colorida, rich resolve isso bem.
pip install rich
import hmac
import hashlib
from rich.console import Console
from rich.table import Table
console = Console()
key = b"debug_signing_key"
messages = {
"/api/v2/orders": b'{"status":"active"}',
"/api/v2/invoices": b'{"status":"pending"}',
"/api/v2/customers": b'{"status":"verified"}',
}
table = Table(title="Assinaturas HMAC-SHA256")
table.add_column("Endpoint", style="cyan")
table.add_column("Assinatura (primeiros 32 chars)", style="green")
for endpoint, body in messages.items():
sig = hmac.new(key, body, hashlib.sha256).hexdigest()
table.add_row(endpoint, sig[:32] + "...")
console.print(table)Trabalhando com Arquivos Grandes — HMAC Incremental
Para arquivos acima de 50 MB aproximadamente, carregar tudo na memória só para calcular um HMAC é desperdício. O método .update() no objeto HMAC permite alimentar dados em partes. Isso mantém o uso de memória constante independentemente do tamanho do arquivo.
import hmac
import hashlib
def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
"""Calcula HMAC-SHA256 de um arquivo sem carregá-lo inteiramente na memória."""
h = hmac.new(key, digestmod=hashlib.sha256)
try:
with open(filepath, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
h.update(chunk)
except OSError as e:
raise OSError(f"Não é possível ler o arquivo '{filepath}': {e}") from e
return h.hexdigest()
# Assina um export de banco de dados de 2 GB
signing_key = b"backup_integrity_key_2026"
signature = hmac_file(signing_key, "/var/backups/db-export-2026-03.sql.gz")
print(f"HMAC-SHA256: {signature}")import hmac
import hashlib
def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
"""Verifica a assinatura HMAC-SHA256 de um arquivo."""
h = hmac.new(key, digestmod=hashlib.sha256)
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return hmac.compare_digest(h.hexdigest(), expected_hex)
# Verifica um artefato baixado
is_valid = verify_file_hmac(
key=b"release_signing_key",
filepath="/tmp/release-v3.2.0.tar.gz",
expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"Integridade do arquivo: {'válido' if is_valid else 'CORROMPIDO'}").update() usa um chunk_size fixo de memória independentemente do tamanho do arquivo. Uso 64 KB por padrão — grande o suficiente para amortizar o overhead de syscall, pequeno o suficiente para caber no cache L2 da maioria dos processadores.Gerar uma Chave HMAC Criptograficamente Segura em Python
Uma chave fraca ou previsível compromete toda a construção HMAC. O módulo secrets (Python 3.6+) fornece bytes aleatórios criptograficamente fortes. Para HMAC-SHA256, use uma chave de 32 bytes. Para HMAC-SHA512, use 64 bytes. Esses valores correspondem ao tamanho do bloco interno dos respectivos algoritmos de hash, que é o comprimento de chave ideal segundo a RFC 2104.
import secrets
# Gera chaves correspondendo ao tamanho do bloco do algoritmo de hash
sha256_key = secrets.token_bytes(32) # 256 bits — para HMAC-SHA256
sha512_key = secrets.token_bytes(64) # 512 bits — para HMAC-SHA512
# Representação hex — segura para arquivos de configuração e variáveis de ambiente
print(f"Chave HMAC-SHA256: {sha256_key.hex()}")
# ex.: "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"Chave HMAC-SHA512: {sha512_key.hex()}")
# string hex de 128 caracteres
# Base64 URL-safe — compacta, segura para cabeçalhos HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Chave Base64: {b64_key}")
# ex.: "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() ou random.randbytes() do módulo random para chaves HMAC. O módulo random padrão usa o PRNG Mersenne Twister, que é previsível após observar 624 saídas. Sempre use secrets.token_bytes() para aleatoriedade sensível à segurança.Comprimento de Chave e Requisitos da RFC 2104
A RFC 2104 especifica que a chave HMAC pode ter qualquer comprimento, mas recomenda uma chave de pelo menos L bytes — onde L é o comprimento da saída da função de hash subjacente. Para HMAC-SHA256, são 32 bytes (256 bits). Chaves menores que L bits reduzem a margem de segurança proporcionalmente. Chaves maiores que o tamanho do bloco do hash (64 bytes para SHA-256, 128 bytes para SHA-512) são primeiro reduzidas por hash ao tamanho do bloco, então não há benefício em usar chaves maiores que o tamanho do bloco. Use 32 bytes para HMAC-SHA256 e 64 bytes para HMAC-SHA512.
Armazenamento Seguro e Rotação de Chaves
Nunca codifique chaves HMAC diretamente no código-fonte. A abordagem padrão para implantações em produção é carregar a chave de uma variável de ambiente na inicialização: os.environ["HMAC_SECRET"].encode(). Para ambientes de maior segurança, armazene as chaves em um sistema de gerenciamento de segredos como AWS Secrets Manager, HashiCorp Vault ou GCP Secret Manager e as recupere em tempo de execução. Esses sistemas fornecem logs de auditoria, controles de acesso e rotação automática sem exigir um novo deploy de código.
Planeje a rotação de chaves desde o início. Quando uma chave é rotacionada, há uma janela em que requisições em trânsito foram assinadas com a chave antiga e falharão na verificação com a nova. A mitigação padrão é um breve período de sobreposição: aceite assinaturas tanto da chave antiga quanto da nova por um curto período (minutos a horas), depois retire a chave antiga. Se uma chave for comprometida — exposta em logs, vazada por um commit no git ou divulgada em um incidente — rotacione imediatamente e trate todas as assinaturas produzidas com a chave comprometida como não confiáveis. Re-verifique quaisquer resultados de verificação em cache e notifique os consumidores dependentes sobre a mudança de chave.
Usando bytearray e memoryview com hmac.new()
A função hmac.new() aceita qualquer objeto bytes-like tanto para o parâmetro key quanto para msg. Isso significa que você pode passar bytes, bytearray ou memoryview diretamente, sem copiar ou converter. Isso importa principalmente em dois cenários: implementações de protocolo de rede onde socket.recv_into() escreve dados em um buffer bytearray pré-alocado, e sistemas de alta vazão onde evitar cópias intermediárias reduz a pressão no GC. Um slice de memoryview é zero-copy: expõe uma janela no buffer original sem alocar nova memória. Em dezenas de milhares de mensagens por segundo, eliminar essas alocações faz uma diferença mensurável na latência e na vazão.
import hmac
import hashlib
# bytearray — bytes mutáveis, útil para buffers de protocolo binário
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')
sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Assinatura do frame: {sig[:32]}...")
# memoryview — slice zero-copy de um buffer maior
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# HMAC apenas dos primeiros 20 bytes sem copiar
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Assinatura do sensor: {sig[:32]}...")Erros Comuns
Vejo os dois primeiros erros em quase toda revisão de código envolvendo handlers de webhook. São fáceis de introduzir sob pressão de tempo e difíceis de detectar sem saber o que procurar.
Problema: O operador == faz curto-circuito no primeiro byte divergente, vazando informações de temporização que permitem a um atacante reconstruir a assinatura esperada incrementalmente.
Solução: Sempre use hmac.compare_digest() para comparação de assinaturas — ela roda em tempo constante independentemente de onde ocorre a divergência.
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig: # VULNERÁVEL a ataque de temporização
process_webhook(body)received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if hmac.compare_digest(received_sig, expected_sig): # tempo constante
process_webhook(body)Problema: hmac.new() exige objetos bytes-like. Passar um str do Python levanta TypeError: "key: expected bytes or bytearray, but got 'str'".
Solução: Chame .encode() em chaves e mensagens em string antes de passá-las para hmac.new().
key = "my_api_secret" # str, não bytes
msg = '{"event":"test"}' # str, não bytes
sig = hmac.new(key, msg, hashlib.sha256) # TypeError!key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)Problema: Chamar hmac.new(key, msg) sem o terceiro argumento levanta TypeError. Antes do Python 3.4 o padrão era MD5, mas o padrão foi removido por razões de segurança.
Solução: Sempre passe o algoritmo explicitamente: hashlib.sha256, hashlib.sha512 ou o que seu protocolo exigir.
# digestmod ausente — levanta TypeError no Python 3.4+ sig = hmac.new(key, msg).hexdigest()
# Sempre especifique o algoritmo de hash sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Problema: Muitos provedores de webhook (Stripe, Shopify) enviam assinaturas em Base64, não em hex. Comparar uma string hex com um valor Base64 sempre falha, fazendo todos os webhooks serem rejeitados.
Solução: Verifique a documentação do provedor para o formato de assinatura. Se usarem Base64, codifique os bytes brutos de .digest(), não a string de .hexdigest().
# Provedor envia Base64, mas calculamos hex — nunca corresponde expected = hmac.new(key, body, hashlib.sha256).hexdigest() # "a3f1b9c0..." vs "o/G5wE59..." — sempre diverge
import base64
# Corresponde ao formato do provedor: bytes brutos → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — corresponde ao cabeçalhostdlib hmac vs cryptography — Comparação Rápida
Use o módulo hmac da stdlib para verificação de webhooks, assinatura de API e operações HMAC gerais — não requer dependências e cobre todos os algoritmos padrão. Use hmac.digest() para operações em lote onde a velocidade one-shot importa. Recorra à biblioteca cryptography apenas quando você já depende dela para outras operações (TLS, X.509, criptografia simétrica) e deseja a API .verify() baseada em exceção. Para verificações rápidas de assinatura sem escrever Python, use a ferramenta HMAC Generator para colar sua chave e mensagem e obter o resultado instantaneamente.
Perguntas Frequentes
Qual é a diferença entre hmac.new() e hmac.digest() em Python?
hmac.new() retorna um objeto HMAC que suporta chamadas incrementais a .update() e oferece tanto .digest() (bytes brutos) quanto .hexdigest() (string hex). hmac.digest() é uma função one-shot adicionada no Python 3.7 que retorna bytes brutos diretamente, sem criar um objeto intermediário. Use hmac.digest() quando tiver a mensagem completa e precisar apenas do resultado. Use hmac.new() quando precisar alimentar dados em partes ou precisar da saída em hex.
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# One-shot — retorna bytes brutos
raw = hmac.digest(key, body, hashlib.sha256)
# Baseado em objeto — suporta atualizações incrementais e saída hex
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()Como verifico uma assinatura HMAC em Python?
Recalcule o HMAC sobre a mensagem original usando o segredo compartilhado, depois compare com hmac.compare_digest(). Nunca use == para comparação de assinaturas. O operador == é vulnerável a ataques de temporização porque faz curto-circuito no primeiro byte divergente, vazando informações sobre o comprimento e conteúdo da assinatura esperada.
import hmac, hashlib
def verify_signature(secret: bytes, message: bytes, received_sig: str) -> bool:
expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_sig)HMAC SHA-256 em Python é igual a um hash com hashlib.sha256?
Não. hashlib.sha256 calcula um hash SHA-256 simples da entrada, que qualquer pessoa pode reproduzir. HMAC-SHA256 mistura uma chave secreta ao cálculo do hash seguindo o RFC 2104, então apenas quem possui a chave consegue produzir ou verificar a saída correta. Um hash simples prova a integridade dos dados. Um HMAC prova tanto a integridade quanto a autenticidade.
import hmac, hashlib msg = b"transfer:9950:USD" key = b"api_secret_k8x2" plain_hash = hashlib.sha256(msg).hexdigest() # qualquer pessoa pode calcular isso hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest() # requer a chave
Posso usar HMAC-SHA1 no Python 3?
Sim, basta passar hashlib.sha1 como argumento digestmod. HMAC-SHA1 ainda funciona normalmente no Python 3 e o módulo hmac não emite alertas de descontinuação para ele. Dito isso, SHA-1 é considerado fraco para novos projetos — sua resistência a colisões está abaixo de 80 bits e o NIST o descontinuou para a maioria dos usos de assinatura digital em 2015. O principal motivo para usar HMAC-SHA1 hoje é compatibilidade retroativa com protocolos existentes que o exigem, como OAuth 1.0a ou certos sistemas legados de webhook. Quando você controla ambos os lados da integração, prefira HMAC-SHA256 ou HMAC-SHA512 em todo trabalho novo.
import hmac, hashlib key = b"oauth_consumer_secret" base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123" sig = hmac.new(key, base_string, hashlib.sha1).digest()
Como gero uma chave HMAC segura em Python?
Use secrets.token_bytes() da biblioteca padrão. Para HMAC-SHA256, uma chave de 32 bytes é a recomendação padrão, pois corresponde ao tamanho do bloco do hash. Para HMAC-SHA512, use 64 bytes. Não use random.random() ou os.urandom() para geração de chaves em código de aplicação — secrets é o módulo correto para aleatoriedade sensível à segurança desde o Python 3.6.
import secrets hmac_sha256_key = secrets.token_bytes(32) # 256 bits hmac_sha512_key = secrets.token_bytes(64) # 512 bits # Armazene como hex para arquivos de configuração print(hmac_sha256_key.hex()) # ex.: "a3f1b9c04e..."
Por que hmac.new() exige digestmod no Python 3?
Antes do Python 3.4, o digestmod padrão era MD5, que é criptograficamente quebrado — MD5 possui ataques de colisão conhecidos e nunca deve ser usado em código novo sensível à segurança. Os mantenedores do Python removeram o padrão para forçar uma escolha explícita de algoritmo, evitando que desenvolvedores entregassem MACs baseados em MD5 sem perceber. Se você chamar hmac.new(key, msg) sem digestmod, receberá um TypeError. Sempre passe o algoritmo explicitamente: hashlib.sha256, hashlib.sha512 ou qualquer outro construtor do hashlib. Na dúvida, hashlib.sha256 é o padrão seguro — sem vulnerabilidades conhecidas e rápido o suficiente para qualquer carga de trabalho prática.
import hmac, hashlib key = b"secret" msg = b"data" # Isso levanta TypeError no Python 3.4+ # hmac.new(key, msg) # TypeError: missing required argument: 'digestmod' # Sempre especifique o algoritmo h = hmac.new(key, msg, hashlib.sha256)
Para uma verificação rápida de HMAC sem precisar rodar um script Python, cole sua chave e mensagem no HMAC Generator online — suporta SHA-256, SHA-384 e SHA-512 com resultados instantâneos.
Ferramentas Relacionadas
Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.