HMAC en Python — hmac.new() SHA-256 con ejemplos de código

·DevOps Engineer & Python Automation Specialist·Revisado porMaria Santos·Publicado

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.

Before · Python
After · Python
# 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".

Python 3.7+ — ejemplo mínimo de HMAC-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.

Python 3.7+ — .digest() vs .hexdigest()
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.

Python 3.7+ — codificación de cadenas a bytes
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 hex
Nota:El parámetro digestmod 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().

Python 3.7+ — codificación Base64 de HMAC-SHA256
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.

Python 3.7+ — HMAC-SHA1
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 Authorization

HMAC-SHA512 — Salida más larga

Python 3.7+ — HMAC-SHA512
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

Python 3.7+ — HMAC-MD5
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"
Advertencia:HMAC-MD5 solo es aceptable para mantener compatibilidad con sistemas que no puedes migrar. Para cualquier proyecto nuevo, usa HMAC-SHA256 como mínimo. MD5 tiene vulnerabilidades de colisión conocidas que, aunque menos explotables directamente en modo HMAC, lo convierten en una mala elección por defecto.

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()

Parámetro
Tipo
Por defecto
Descripción
key
bytes | bytearray
(obligatorio)
La clave secreta — debe ser bytes, no una cadena de texto
msg
bytes | None
None
Mensaje inicial a firmar; se pueden añadir más datos con .update()
digestmod
str | callable
(obligatorio)
Algoritmo de hash — p. ej. hashlib.sha256 o la cadena "sha256"

hmac.digest() de una sola llamada (Python 3.7+)

Parámetro
Tipo
Descripción
key
bytes
La clave secreta
msg
bytes
El mensaje a autenticar
digest
str | callable
Algoritmo de hash — igual que digestmod en hmac.new()

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.

Python 3.7+ — hmac.digest() de una sola llamada
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.

Nota: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

Python 3.7+ — verificación de HMAC de webhook (Flask)
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 "", 200

La 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.

Python 3.7+ — verificación de X-Hub-Signature-256 de GitHub
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 "", 200

El 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.

Python 3.7+ — firma de solicitudes a API con HMAC-SHA256
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

Python 3.7+ — solicitudes firmadas con HMAC usando 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.

bash — HMAC-SHA256 con Python en una línea
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# salida: cadena hex de 64 caracteres
bash — HMAC-SHA256 con openssl (para comparación)
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
bash — HMAC desde una variable de entorno
# 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())
"
Nota:El flag 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.

bash — instalar cryptography
pip install cryptography
Python 3.7+ — HMAC con la biblioteca 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 incorrecto

El 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.

bash — instalar rich
pip install rich
Python 3.7+ — salida HMAC en color con 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)
Nota:Rich es solo para mostrar en terminal. No lo uses cuando escribas firmas HMAC en archivos, cabeceras HTTP o sistemas de agregación de logs — los códigos de escape ANSI corromperán la salida.

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.

Python 3.7+ — HMAC de un archivo grande en fragmentos
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}")
Python 3.7+ — verificar la firma HMAC de un archivo descargado
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'}")
Nota:Cambia al HMAC por fragmentos cuando el archivo supere los 50-100 MB o cuando proceses subidas en un servidor web donde la memoria por solicitud importa. El enfoque con .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.

Python 3.7+ — generar claves HMAC con secrets
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="
Advertencia:Nunca uses 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.

Python 3.7+ — bytearray y memoryview con HMAC
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.

Comparar firmas HMAC con == en lugar de hmac.compare_digest()

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.

Before · Python
After · Python
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)
Pasar una cadena en lugar de bytes a hmac.new()

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().

Before · Python
After · Python
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)
Olvidar digestmod (Python 3.4+)

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.

Before · Python
After · Python
# 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()
Usar .hexdigest() cuando el proveedor espera Base64

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().

Before · Python
After · Python
# 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 cabecera

stdlib hmac vs cryptography — Comparación rápida

Método
Algoritmo
Salida
Streaming
Tipos personalizados
Requiere instalación
hmac.new() + hexdigest()
Cualquier hashlib
Cadena hex
✓ via .update()
N/A
No (stdlib)
hmac.new() + digest()
Cualquier hashlib
Bytes en bruto
✓ via .update()
N/A
No (stdlib)
hmac.digest()
Cualquier hashlib
Bytes en bruto
✗ (una sola llamada)
N/A
No (stdlib, 3.7+)
hashlib.sha256 (hash simple)
Solo SHA-256
Hex o bytes
✓ via .update()
N/A
No (stdlib)
cryptography (HMAC)
Cualquiera
Bytes en bruto
✓ via .update()
N/A
pip install
pyca/cryptography + CMAC
AES-CMAC
Bytes en bruto
✓ via .update()
N/A
pip install

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.

Python
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.

Python
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.

Python
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.

Python
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.

Python
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.

Python
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

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

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.

MS
Maria SantosRevisor técnico

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.