HMAC en Python — hmac.new() SHA-256 con ejemplos de código
Usa el Generador HMAC gratuito directamente en tu navegador — sin instalación.
Probar Generador HMAC online →Cada callback de webhook, cada solicitud firmada a una API, cada notificación de evento de Stripe o GitHub usa una firma HMAC para demostrar que el payload no ha sido manipulado. El módulo hmac de Python gestiona HMAC-SHA256 en Python con una sola llamada a función: hmac.new(key, msg, hashlib.sha256). Sin pip install, sin extensiones C, sin dependencias de terceros. Para verificaciones rápidas de firma sin escribir código, el Generador HMAC online te da el resultado al instante. Esta guía cubre hmac.new(), hmac.digest(), hmac.compare_digest(), codificación Base64, verificación de webhooks, firma de solicitudes a API y todos los algoritmos de hash desde SHA-1 hasta SHA-512. Todos los ejemplos apuntan a Python 3.7+.
- ✓hmac.new(key, msg, hashlib.sha256) es el punto de entrada estándar — key y msg deben ser bytes, digestmod es obligatorio desde Python 3.4.
- ✓hmac.digest(key, msg, "sha256") es una alternativa de una sola llamada más rápida añadida en Python 3.7 — devuelve bytes en bruto, sin objeto intermedio.
- ✓Verifica siempre las firmas con hmac.compare_digest() para prevenir ataques de temporización — nunca uses == para comparar HMAC.
- ✓Codifica en Base64 la salida en bruto de .digest() para cabeceras HTTP y firmas de webhooks: base64.b64encode(h.digest()).
- ✓El módulo hmac acepta cualquier algoritmo de hashlib: sha1, sha256, sha384, sha512, md5, blake2b.
¿Qué es HMAC?
HMAC (Hash-based Message Authentication Code) es una construcción definida en RFC 2104 que combina una clave secreta con una función de hash para producir una etiqueta de autenticación de tamaño fijo. A diferencia de un hash simple (que cualquiera puede calcular), un HMAC requiere conocer la clave secreta. Esto significa que puedes usarlo para verificar tanto la integridad como la autenticidad de un mensaje. Si cambia aunque sea un solo byte del mensaje o de la clave, la salida es completamente diferente. La construcción funciona aplicando hash a la clave en XOR con dos constantes de relleno distintas (ipad y opad), envolviendo el mensaje entre dos operaciones de hash. El módulo hmac de Python implementa este RFC directamente.
# Hash SHA-256 simple — sin clave secreta, cualquiera puede calcularlo hashlib.sha256(b"payment:9950:USD").hexdigest() # "7a3b1c..." (determinista, público)
# HMAC-SHA256 — requiere la clave secreta para producirlo hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest() # "e4f2a8..." (solo quien tiene la clave puede calcularlo)
hmac.new() — El punto de entrada de la biblioteca estándar
El módulo hmac forma parte de la biblioteca estándar de Python. Con dos imports estás listo: import hmac, hashlib. Las tres funciones principales son hmac.new() (crea un objeto HMAC), hmac.digest() (una sola llamada, Python 3.7+) y hmac.compare_digest() (comparación en tiempo constante). No requiere pip install.
hmac.new(key, msg, digestmod) recibe tres argumentos. Tanto key como msg deben ser objetos similares a bytes ( bytes, bytearray o memoryview). El argumento digestmod es obligatorio desde Python 3.4 y acepta cualquier constructor de hashlib (como hashlib.sha256) o un nombre en cadena como "sha256".
import hmac
import hashlib
key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'
# Crea el objeto HMAC y obtén la firma en hex
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"El objeto HMAC expone dos métodos de salida. .digest() devuelve bytes en bruto (32 bytes para SHA-256, 64 para SHA-512). .hexdigest() devuelve una cadena hex en minúsculas. La cadena hex es un str de Python simple — no se necesita decodificar.
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 # Representan los mismos datos — hex es simplemente una codificación en cadena de los bytes assert raw_bytes.hex() == hex_string
Si tu clave o mensaje es una cadena de Python, llama a .encode() para convertirla a bytes antes de pasarla a hmac.new(). Esto confunde a casi todo el mundo la primera vez — las cadenas de Python 3 son Unicode, no bytes, y el módulo hmac las rechaza.
import hmac
import hashlib
# Clave y mensaje como cadenas — .encode() convierte a 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..." — salida consistente en cadena hexdigestmod no tiene valor por defecto desde Python 3.4. Llamar a hmac.new(key, msg) sin él lanza un TypeError. Antes de la versión 3.4, usaba MD5 por defecto, razón por la que los mantenedores de Python eliminaron ese comportamiento — obligándote a hacer una elección explícita y segura.HMAC-SHA256 Base64, SHA-1, SHA-512 y MD5
La función hmac.new() funciona con cualquier algoritmo de hash disponible en hashlib. La mayoría de proveedores de webhooks y pasarelas de API usan HMAC-SHA256, pero encontrarás SHA-1 en OAuth 1.0a, SHA-512 en protocolos que lo exigen, y MD5 en sistemas heredados que no se han actualizado.
HMAC-SHA256 con salida Base64
Muchos proveedores de webhooks envían la firma como una cadena codificada en Base64 en una cabecera HTTP. Para producir el mismo formato, pasa los bytes en bruto de .digest() a base64.b64encode().
import hmac
import hashlib
import base64
key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'
# Digest en bruto → Base64 (común en cabeceras Authorization y firmas de webhooks)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")
print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="
# Este es el valor que compararías contra la cabecera X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="HMAC-SHA1 — Compatibilidad con protocolos heredados
SHA-1 se considera débil para nuevos diseños, pero HMAC-SHA1 sigue siendo necesario en OAuth 1.0a y algunas implementaciones antiguas de webhooks. El código es idéntico — simplemente cambia el 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 clave de firma
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..." — codifica en URL esto para la cabecera AuthorizationHMAC-SHA512 — Salida más larga
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 — Solo para sistemas heredados
import hmac import hashlib # MD5 está criptográficamente roto — úsalo solo por compatibilidad con protocolos heredados key = b"legacy_api_key" msg = b"action=charge&amount=1500&merchant=store_42" sig = hmac.new(key, msg, hashlib.md5).hexdigest() print(sig) # cadena hex de 32 caracteres # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Referencia de parámetros de hmac.new()
La firma del constructor es hmac.new(key, msg=None, digestmod). Las tres funciones del módulo comparten el mismo patrón para los argumentos de clave y algoritmo.
Constructor hmac.new()
hmac.digest() de una sola llamada (Python 3.7+)
El parámetro digestmod acepta tanto un callable (como hashlib.sha256) como un nombre en cadena (como "sha256"). Se prefiere la forma callable porque se valida en el momento de la importación — un error tipográfico en la cadena solo falla en tiempo de ejecución.
hmac.digest() — HMAC de una sola llamada y rápido (Python 3.7+)
Python 3.7 añadió hmac.digest(key, msg, digest) como función a nivel de módulo. Calcula el HMAC en una sola llamada sin crear un objeto HMAC intermedio. El valor de retorno son bytes en bruto (equivalente a llamar a .digest() sobre el objeto). Esta función usa una implementación C optimizada en CPython y evita la sobrecarga de asignación del objeto, lo que la hace measurably más rápida en bucles de alta frecuencia.
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 de una sola llamada — sin objeto HMAC intermedio
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]
# Convertir a hex para mostrar
for msg, sig in zip(messages, signatures):
print(f"{msg[:30]}... -> {sig.hex()[:24]}...")La limitación: hmac.digest() solo devuelve bytes en bruto. Si necesitas la cadena hex directamente, aún necesitas hmac.new() para su método .hexdigest(), o encadena .hex() sobre el resultado en bytes.
hmac.digest() no admite llamadas incrementales a .update(). Si estás leyendo un archivo grande en fragmentos y necesitas calcular el HMAC del contenido, usa hmac.new() y llama a .update(chunk) en un bucle.Verificar firmas HMAC de webhooks y respuestas de API
El uso más común de HMAC en Python es verificar firmas de webhooks. Todos los proveedores principales (Stripe, GitHub, Shopify, Twilio) firman los payloads con HMAC-SHA256 y envían la firma en una cabecera. El patrón es siempre el mismo: recalcula el HMAC sobre el cuerpo de la solicitud en bruto y luego compara con hmac.compare_digest().
Verificación de firma 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én el cuerpo en bruto — debe coincidir exactamente con lo que se firmó
raw_body = request.get_data()
# Obtén la firma de la cabecera
received_sig = request.headers.get("X-Signature-256", "")
# Recalcula el HMAC sobre el cuerpo en bruto
expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
# Comparación en tiempo constante — previene ataques de temporización
if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
abort(403, "Invalid signature")
# Firma verificada — procesa el evento
event = request.get_json()
print(f"Verified event: {event['type']} for {event['data']['amount']}")
return "", 200La función hmac.compare_digest() compara dos cadenas o secuencias de bytes en tiempo constante. Una comparación regular con == se detiene en el primer byte que no coincide. Un atacante puede medir el tiempo de respuesta en múltiples solicitudes y reconstruir gradualmente la firma esperada byte a byte. La comparación en tiempo constante elimina este canal lateral.
Verificación de webhook de GitHub
El formato de webhook de GitHub ilustra el patrón completo. Envía una cabecera X-Hub-Signature-256 que contiene sha256= seguido del HMAC-SHA256 en hex del cuerpo de la solicitud en bruto, firmado con el secreto de webhook que configuras en los ajustes de tu repositorio. La diferencia clave respecto a la verificación genérica de webhooks es que debes eliminar el prefijo sha256= antes de comparar, y debes leer los bytes en bruto del cuerpo de la solicitud — parsear JSON primero cambia la representación en bytes y rompe la verificación.
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 envía: X-Hub-Signature-256: sha256=<hex_digest>
sig_header = request.headers.get("X-Hub-Signature-256", "")
if not sig_header.startswith("sha256="):
abort(403, "Missing or malformed signature header")
received_hex = sig_header[len("sha256="):]
raw_body = request.get_data() # bytes en bruto — no parsear JSON antes
expected_hex = hmac.new(
GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_hex, received_hex):
abort(403, "Signature mismatch — payload may have been tampered with")
event_type = request.headers.get("X-GitHub-Event", "unknown")
payload = request.get_json()
print(f"Verified GitHub {event_type} event: {payload.get('action', '')}")
return "", 200El mismo patrón aplica a Shopify (X-Shopify-Hmac-SHA256) y Twilio (X-Twilio-Signature), con la única diferencia siendo el nombre de la cabecera y si la firma está en hex o codificada en Base64. Siempre consulta la documentación del proveedor para confirmar el formato de codificación — mezclar hex y Base64 es la causa más común de errores de discrepancia en firmas.
Autenticación de solicitudes a API con HMAC
Algunas APIs requieren que el cliente firme cada solicitud con HMAC. La cadena firmada generalmente incluye el método HTTP, la ruta, la marca de tiempo y el cuerpo de la solicitud. Aquí hay un patrón para autenticación interna de servicio a servicio.
import hmac
import hashlib
import time
import json
def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
"""Genera una firma HMAC-SHA256 para una solicitud a la API."""
timestamp = str(int(time.time()))
# Construye la cadena de firma — método + ruta + marca de tiempo + cuerpo
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..."}Firma de solicitudes HTTP con la 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, determinista
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"Signed request failed: {e}") from e
# Envía una solicitud POST firmada
resp = make_signed_request("POST", "/api/v2/invoices", {
"customer_id": "cust_4421",
"line_items": [
{"description": "Pro plan - March 2026", "amount": 4900},
{"description": "Extra seats (3)", "amount": 2100},
],
})
print(resp.status_code, resp.json())Nota rápida: usa separators=(",", ":") al serializar el cuerpo para la firma. El comportamiento por defecto de json.dumps() añade espacios después de los separadores, lo que cambia la representación en bytes y rompe la verificación si el servidor serializa de forma diferente. El JSON compacto te da una forma canónica.
Generación de HMAC desde la línea de comandos
A veces necesitas calcular un HMAC sin escribir un script. El flag -c de Python y openssl manejan esto desde el terminal.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # salida: cadena hex de 64 caracteres
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret" # SHA2-256(stdin)= 7d11... # Pasar un archivo por openssl HMAC openssl dgst -sha256 -hmac "my_secret" < payload.json
# Almacena la clave en variable de entorno para evitar exposición en el historial del 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 es fundamental — sin él, echo añade un carácter de nueva línea al mensaje, lo que cambia la salida HMAC. Esta es la causa más común de discrepancias en firmas al depurar desde el terminal.Alternativa de alto rendimiento — biblioteca cryptography
Para la mayoría de aplicaciones, el módulo estándar hmac es suficientemente rápido. Si ya usas la biblioteca cryptography para TLS o gestión de certificados, también proporciona HMAC respaldado por OpenSSL. La principal diferencia práctica respecto a la stdlib es la API basada en excepciones de .verify() descrita a continuación — lanza una excepción en caso de discrepancia en lugar de devolver un booleano que podrías olvidar comprobar.
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 en bruto
print(signature.hex())
# "9c4e2a..."
# Modo de verificación — lanza InvalidSignature si no coincide
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature) # lanza cryptography.exceptions.InvalidSignature si es incorrectoEl método .verify() de la biblioteca cryptography es especialmente útil: lanza una excepción en caso de discrepancia en lugar de devolver un booleano. Esto hace más difícil ignorar accidentalmente un fallo de verificación. El hmac.compare_digest() de la biblioteca estándar devuelve True/ False, que puede ignorarse silenciosamente si el desarrollador olvida comprobar el valor de retorno.
Salida en terminal con resaltado de sintaxis
Si estás depurando firmas HMAC en el terminal y quieres salida con colores, rich lo gestiona bien.
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="HMAC-SHA256 Signatures")
table.add_column("Endpoint", style="cyan")
table.add_column("Signature (first 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)Trabajo con archivos grandes — HMAC incremental
Para archivos de más de unos 50 MB, cargar todo en memoria solo para calcular un HMAC es ineficiente. El método .update() del objeto HMAC te permite alimentar datos en fragmentos. Esto mantiene el uso de memoria constante independientemente del tamaño del archivo.
import hmac
import hashlib
def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
"""Calcula HMAC-SHA256 de un archivo sin cargarlo completamente en memoria."""
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"Cannot read file '{filepath}': {e}") from e
return h.hexdigest()
# Firma un export de base de datos 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 la firma HMAC-SHA256 de un archivo."""
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 un artefacto descargado
is_valid = verify_file_hmac(
key=b"release_signing_key",
filepath="/tmp/release-v3.2.0.tar.gz",
expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"Integridad del archivo: {'válido' if is_valid else 'CORRUPTO'}").update() usa una cantidad fija de memoria definida por chunk_size independientemente del tamaño del archivo. El valor por defecto de 64 KB es lo suficientemente grande para amortizar la sobrecarga de las llamadas al sistema, y lo suficientemente pequeño para mantenerse dentro de la caché L2 de la mayoría del hardware.Genera una clave HMAC criptográficamente segura en Python
Una clave débil o predecible compromete toda la construcción HMAC. El módulo secrets (Python 3.6+) proporciona bytes aleatorios criptográficamente seguros. Para HMAC-SHA256, usa una clave de 32 bytes. Para HMAC-SHA512, usa 64 bytes. Estos coinciden con el tamaño de bloque interno de los respectivos algoritmos de hash, que es la longitud óptima de clave según el RFC 2104.
import secrets
# Genera claves que coincidan con el tamaño de bloque del 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
# Representación hex — segura para archivos de configuración y variables de entorno
print(f"Clave HMAC-SHA256: {sha256_key.hex()}")
# p. ej. "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"Clave HMAC-SHA512: {sha512_key.hex()}")
# cadena hex de 128 caracteres
# Base64 segura para URL — compacta, segura para cabeceras HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Clave Base64: {b64_key}")
# p. ej. "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() ni random.randbytes() del módulo random para claves HMAC. El módulo random por defecto usa el PRNG Mersenne Twister, que es predecible tras observar 624 salidas. Usa siempre secrets.token_bytes() para aleatoriedad sensible a la seguridad.Longitud de clave y requisitos del RFC 2104
El RFC 2104 especifica que la clave HMAC puede tener cualquier longitud, pero recomienda una clave de al menos L bytes — donde L es la longitud de salida de la función de hash subyacente. Para HMAC-SHA256, son 32 bytes (256 bits). Las claves más cortas que L bits reducen el margen de seguridad proporcionalmente. Las claves más largas que el tamaño de bloque del hash (64 bytes para SHA-256, 128 bytes para SHA-512) se reducen primero al tamaño de bloque mediante hash, por lo que no hay beneficio en usar claves más largas que el tamaño de bloque. Usa 32 bytes para HMAC-SHA256 y 64 bytes para HMAC-SHA512.
Almacenamiento seguro de claves y rotación
Nunca insertes claves HMAC directamente en el código fuente. El enfoque estándar para despliegues en producción es cargar la clave desde una variable de entorno al arrancar: os.environ["HMAC_SECRET"].encode(). Para entornos de mayor seguridad, almacena las claves en un sistema de gestión de secretos como AWS Secrets Manager, HashiCorp Vault o GCP Secret Manager y obtenlas en tiempo de ejecución. Estos sistemas proporcionan registros de auditoría, controles de acceso y rotación automática sin necesidad de un redespliegue de código.
Planifica la rotación de claves desde el principio. Cuando se rota una clave, hay un período en el que las solicitudes en tránsito se firmaron con la clave antigua y fallarán la verificación contra la nueva. La mitigación estándar es un breve período de superposición: acepta firmas tanto de la clave antigua como de la nueva durante un tiempo corto (minutos u horas), luego retira la clave antigua. Si una clave se ve comprometida — expuesta en logs, filtrada mediante un commit de git o revelada en un incidente — rótala de inmediato y trata todas las firmas producidas con la clave comprometida como no fiables. Vuelve a verificar cualquier resultado de verificación en caché y notifica a los consumidores de la clave del cambio.
Uso de bytearray y memoryview con hmac.new()
La función hmac.new() acepta cualquier objeto similar a bytes tanto para la clave como para los parámetros msg. Esto significa que puedes pasar bytes, bytearray o memoryview directamente, sin copiar ni convertir. Esto importa principalmente en dos escenarios: implementaciones de protocolos de red donde socket.recv_into() escribe datos en un buffer bytearray preasignado, y sistemas de alto rendimiento donde evitar copias intermedias reduce la presión sobre el recolector de basura. Una porción de memoryview es de copia cero: expone una ventana sobre el buffer original sin asignar nueva memoria. Con decenas de miles de mensajes por segundo, eliminar esas asignaciones marca una diferencia measurable en latencia y rendimiento.
import hmac
import hashlib
# bytearray — bytes mutables, útil para buffers de protocolos binarios
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"Frame signature: {sig[:32]}...")
# memoryview — porción de copia cero de un buffer mayor
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# HMAC solo de los primeros 20 bytes sin copiar
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")Errores comunes
Los dos primeros errores aparecen en casi todas las revisiones de código que involucran manejadores de webhooks. Son fáciles de introducir bajo presión de tiempo y difíciles de detectar sin saber qué buscar.
Problema: El operador == se detiene en el primer byte que no coincide, filtrando información de temporización que permite a un atacante reconstruir la firma esperada de forma incremental.
Solución: Usa siempre hmac.compare_digest() para la comparación de firmas — se ejecuta en tiempo constante independientemente de dónde ocurra la discrepancia.
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig: # VULNERABLE a ataque de temporización
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): # tiempo constante
process_webhook(body)Problema: hmac.new() requiere objetos similares a bytes. Pasar un str de Python lanza TypeError: "key: expected bytes or bytearray, but got 'str'".
Solución: Llama a .encode() en las claves y mensajes de tipo cadena antes de pasarlos a hmac.new().
key = "my_api_secret" # str, no bytes
msg = '{"event":"test"}' # str, no 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: Llamar a hmac.new(key, msg) sin el tercer argumento lanza TypeError. Antes de Python 3.4 usaba MD5 por defecto, pero ese valor predeterminado se eliminó por razones de seguridad.
Solución: Pasa siempre el algoritmo explícitamente: hashlib.sha256, hashlib.sha512, o el que requiera tu protocolo.
# Falta digestmod — lanza TypeError en Python 3.4+ sig = hmac.new(key, msg).hexdigest()
# Siempre especifica el algoritmo de hash sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Problema: Muchos proveedores de webhooks (Stripe, Shopify) envían firmas codificadas en Base64, no en hex. Comparar una cadena hex contra un valor Base64 siempre falla, haciendo que todos los webhooks sean rechazados.
Solución: Consulta la documentación del proveedor para conocer el formato de firma. Si usan Base64, codifica los bytes en bruto de .digest(), no la cadena .hexdigest().
# El proveedor envía Base64, pero calculamos hex — nunca coincide expected = hmac.new(key, body, hashlib.sha256).hexdigest() # "a3f1b9c0..." vs "o/G5wE59..." — siempre discrepancia
import base64
# Coincide con el formato del proveedor: bytes en bruto → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — coincide con la cabecerastdlib hmac vs cryptography — Comparación rápida
Usa el módulo hmac de la stdlib para verificación de webhooks, firma de API y operaciones HMAC generales — no requiere dependencias y cubre todos los algoritmos estándar. Usa hmac.digest() para operaciones en lote donde importa la velocidad de la llamada única. Recurre a la biblioteca cryptography solo cuando ya dependas de ella para otras operaciones (TLS, X.509, cifrado simétrico) y quieras la API .verify() basada en excepciones. Para verificaciones rápidas de firma sin escribir Python, usa la herramienta Generador HMAC para pegar tu clave y mensaje y obtener el resultado al instante.
Preguntas frecuentes
¿Cuál es la diferencia entre hmac.new() y hmac.digest() en Python?
hmac.new() devuelve un objeto HMAC que admite llamadas incrementales a .update() y ofrece tanto .digest() (bytes en bruto) como .hexdigest() (cadena hex). hmac.digest() es una función de una sola llamada añadida en Python 3.7 que devuelve bytes en bruto directamente sin crear un objeto intermedio. Usa hmac.digest() cuando tienes el mensaje completo y solo necesitas el resultado. Usa hmac.new() cuando necesites alimentar datos en fragmentos o necesites la salida en hex.
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# Una sola llamada — devuelve bytes en bruto
raw = hmac.digest(key, body, hashlib.sha256)
# Basado en objeto — admite actualizaciones incrementales y salida hex
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()¿Cómo verifico una firma HMAC en Python?
Recalcula el HMAC sobre el mensaje original usando el secreto compartido y luego compara con hmac.compare_digest(). Nunca uses == para comparar firmas. El operador == es vulnerable a ataques de temporización porque se detiene en el primer byte que no coincide, filtrando información sobre la longitud y el contenido de la firma 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)¿Es el HMAC SHA-256 de Python lo mismo que calcular un hash con hashlib.sha256?
No. hashlib.sha256 calcula un hash SHA-256 simple de la entrada, que cualquiera puede reproducir. HMAC-SHA256 mezcla una clave secreta en el cálculo del hash siguiendo el RFC 2104, de modo que solo quien tenga la clave puede producir o verificar la salida correcta. Un hash simple demuestra integridad de datos. Un HMAC demuestra tanto integridad como autenticidad.
import hmac, hashlib msg = b"transfer:9950:USD" key = b"api_secret_k8x2" plain_hash = hashlib.sha256(msg).hexdigest() # cualquiera puede calcularlo hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest() # requiere la clave
¿Puedo usar HMAC-SHA1 en Python 3?
Sí, pasa hashlib.sha1 como argumento digestmod. HMAC-SHA1 funciona perfectamente en Python 3 y el módulo hmac no tiene advertencias de obsolescencia al respecto. Dicho esto, SHA-1 se considera débil para nuevos diseños — su resistencia a colisiones está por debajo de los 80 bits y NIST lo deprecó para la mayoría de usos de firma digital en 2015. La razón principal para usar HMAC-SHA1 hoy es la compatibilidad con protocolos existentes que lo exigen, como OAuth 1.0a o ciertos sistemas de webhooks heredados. Cuando controlas ambos extremos de la integración, prefiere HMAC-SHA256 o HMAC-SHA512 para todo trabajo nuevo.
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()
¿Cómo genero una clave HMAC segura en Python?
Usa secrets.token_bytes() de la biblioteca estándar. Para HMAC-SHA256, una clave de 32 bytes es la recomendación estándar ya que coincide con el tamaño del bloque del hash. Para HMAC-SHA512, usa 64 bytes. No uses random.random() ni os.urandom() para la generación de claves en código de aplicación — secrets es el módulo correcto para aleatoriedad sensible a la seguridad desde Python 3.6.
import secrets hmac_sha256_key = secrets.token_bytes(32) # 256 bits hmac_sha512_key = secrets.token_bytes(64) # 512 bits # Almacenar como hex para archivos de configuración print(hmac_sha256_key.hex()) # p. ej. "a3f1b9c04e..."
¿Por qué hmac.new() requiere digestmod en Python 3?
Antes de Python 3.4, digestmod usaba MD5 por defecto, que está criptográficamente roto — MD5 tiene ataques de colisión conocidos y nunca debería usarse en código nuevo sensible a la seguridad. Los mantenedores de Python eliminaron el valor por defecto para forzar una elección explícita de algoritmo, evitando que los desarrolladores entreguen MACs basados en MD5 sin darse cuenta. Si llamas a hmac.new(key, msg) sin digestmod, obtienes un TypeError. Siempre pasa el algoritmo explícitamente: hashlib.sha256, hashlib.sha512, o cualquier otro constructor de hashlib. Cuando tengas dudas, hashlib.sha256 es la opción segura por defecto — sin debilidades conocidas y suficientemente rápido para cualquier carga de trabajo práctica.
import hmac, hashlib key = b"secret" msg = b"data" # Esto lanza TypeError en Python 3.4+ # hmac.new(key, msg) # TypeError: missing required argument: 'digestmod' # Siempre especifica el algoritmo h = hmac.new(key, msg, hashlib.sha256)
Para una verificación rápida de HMAC sin arrancar un script de Python, pega tu clave y mensaje en el Generador HMAC online — admite SHA-256, SHA-384 y SHA-512 con resultados al instante.
Herramientas 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.