HMAC en Python: autentica mensajes

Publicado el: 19/08/2026
Tempo de leitura: 5 minutos
Pantalla de verificación de cuenta que representa autenticación de mensajes con HMAC en Python

HMAC es una construcción criptográfica que combina una clave secreta con una función hash para producir un código de autenticación de mensaje. A diferencia de un hash normal, solo quien conoce la clave puede recalcular el valor correcto. Un HMAC válido indica que el contenido no fue modificado y que fue generado por una parte que poseía el secreto compartido.

El módulo hmac de la biblioteca estándar implementa el algoritmo definido en RFC 2104. Se utiliza para validar webhooks, autenticar mensajes entre servicios, proteger configuraciones distribuidas, firmar cookies sencillas y verificar payloads de colas o APIs. Esta guía cubre generación, validación segura, canonicalización, replay attacks y rotación de claves.

Hash normal o HMAC

Un digest SHA-256 no contiene ningún secreto. Cualquier persona que cambie el mensaje puede calcular otro hash. Un checksum confiable detecta corrupción, pero no autentica por sí solo al remitente.

HMAC incorpora una clave mediante una construcción estandarizada. No inventes alternativas como sha256(secret + message). Las combinaciones caseras pueden introducir problemas de extensión, límites ambiguos o gestión incorrecta de la clave.

Consulta la guía de hashlib en Python para hashes generales y el artículo de contraseñas y login seguro para almacenamiento de credenciales. HMAC resuelve un problema diferente: autenticación de mensajes.

Crear HMAC-SHA256

import hmac
import hashlib

key = b"secreto-aleatorio-del-servidor"
message = b"pedido=123&importe=49.90"

mac = hmac.new(key, message, digestmod=hashlib.sha256)
print(mac.hexdigest())

Clave y mensaje deben ser objetos bytes-like. Para texto, define UTF-8 de forma explícita:

key = "secreto del servidor".encode("utf-8")
message = "acción=confirmar".encode("utf-8")

digestmod es obligatorio. SHA-256 es una elección habitual. HMAC requiere un hash de longitud fija, por lo que SHAKE-128 y SHAKE-256 no son compatibles.

Atajo hmac.digest()

Cuando todo el mensaje ya está en memoria, hmac.digest() ofrece una llamada compacta y puede usar una implementación optimizada.

signature = hmac.digest(key, message, "sha256")
print(signature.hex())

Para streams o archivos grandes, crea un objeto HMAC y llama a update() por bloques.

Procesamiento incremental

from pathlib import Path
import hmac
import hashlib

def hmac_file(path: Path, key: bytes) -> str:
    mac = hmac.new(key, digestmod=hashlib.sha256)
    with path.open("rb") as file:
        while chunk := file.read(1024 * 1024):
            mac.update(chunk)
    return mac.hexdigest()

Este patrón evita cargar archivos completos en memoria y sigue el enfoque del artículo sobre leer archivos gigantes con Python.

Verificación con compare_digest()

No compares tags de autenticación con ==. Una comparación normal puede detenerse en la primera diferencia, generando variaciones de tiempo. Muchas mediciones podrían revelar información.

def verify_signature(
    key: bytes,
    message: bytes,
    supplied_hex: str,
) -> bool:
    expected = hmac.new(
        key,
        message,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, supplied_hex)

Los valores deben tener el mismo tipo. Para strings usa solo ASCII, como un digest hexadecimal. Las diferencias de longitud pueden revelar el tamaño, así que valida el formato antes de comparar.

Validar un webhook

Un webhook normalmente envía el cuerpo bruto y una firma en una cabecera. Calcula HMAC sobre exactamente los bytes definidos por el proveedor.

def verify_webhook(body: bytes, header: str, secret: bytes) -> None:
    if not header.startswith("sha256="):
        raise ValueError("Formato de firma inválido")

    supplied = header.removeprefix("sha256=")
    if len(supplied) != 64:
        raise ValueError("Longitud de firma inválida")

    expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, supplied):
        raise PermissionError("Firma inválida")

No conviertas JSON a objetos y lo serialices de nuevo antes de verificar. Espacios, orden de claves, escapes y saltos de línea modifican los bytes. Lee el cuerpo bruto, valida la firma y después procesa el JSON.

Canonicalización del mensaje

Cuando defines un protocolo, especifica campos, orden, encoding y separadores. Una concatenación ingenua puede ser ambigua: ("ab", "c") y ("a", "bc") producen lo mismo sin límites.

def canonical_message(timestamp: int, method: str, path: str, body: bytes) -> bytes:
    prefix = f"v1\n{timestamp}\n{method.upper()}\n{path}\n".encode("utf-8")
    return prefix + body

Incluye una versión para permitir que el esquema evolucione sin confundir firmas nuevas y antiguas.

Protección contra replay

Un HMAC válido no impide que un atacante capture y reenvíe la misma solicitud. Los comandos sensibles deben autenticar timestamp, nonce o identificador de evento.

import time

MAX_AGE = 300
if abs(time.time() - timestamp) > MAX_AGE:
    raise PermissionError("Mensaje expirado")

Guarda los nonces aceptados durante la ventana. El orden seguro es: validar estructura y límites, comprobar tiempo, calcular HMAC, comparar, registrar el nonce y finalmente ejecutar la acción.

Generación y almacenamiento de claves

Una clave HMAC necesita suficiente entropía y debe proceder de una fuente criptográficamente segura.

import secrets
key = secrets.token_bytes(32)

No derives la clave de una frase corta, no la incluyas en el repositorio ni la muestres en logs. Usa un secret manager, una variable de entorno protegida o un archivo con permisos restrictivos.

Rotación de claves

Asocia cada clave a un identificador público como key_id. Durante la rotación firma solo con la nueva, pero acepta temporalmente la anterior para mensajes creados antes del cambio.

KEYS = {
    "2026-08": b"nueva-clave...",
    "2026-07": b"clave-anterior...",
}

Valida y limita el identificador. No pruebes cientos de claves para cada solicitud controlada por un atacante.

Truncamiento de la firma

Algunos protocolos transmiten solo una parte del HMAC para ahorrar espacio. Esto reduce la resistencia frente a falsificación. Prefiere el digest completo salvo que un estándar revisado defina un mínimo aceptable. Nunca aceptes prefijos de longitud arbitraria.

Firmas de API

Los esquemas de firma suelen incluir método HTTP, ruta canónica, query ordenada, timestamp y hash del cuerpo. Cliente y servidor deben normalizar todo de forma idéntica. La guía para integrar APIs con Python complementa estas prácticas con timeouts y validación.

HMAC no cifra

El mensaje sigue siendo visible. HMAC proporciona integridad y autenticidad, no confidencialidad. Usa TLS para transporte y cifrado autenticado para datos secretos almacenados. No reutilices una clave para HMAC y cifrado; deriva claves separadas.

Errores frecuentes

Los fallos más comunes son comparar con ==, firmar JSON reserializado, omitir timestamp, aceptar tags de longitud variable, usar claves predecibles, registrar secretos, reutilizar una clave para varios propósitos y ejecutar la acción antes de verificar.

Buenas prácticas

Usa HMAC-SHA256, una clave aleatoria de al menos 32 bytes, un mensaje canónico versionado y compare_digest(). Autentica timestamp y nonce, limita tamaños, rota claves por identificador y registra únicamente metadatos no sensibles. Prefiere la biblioteca oficial del proveedor cuando su esquema sea complejo.

Conclusión

El módulo hmac proporciona una forma estándar de autenticar mensajes con una clave secreta y una función hash. La llamada es sencilla; la seguridad depende de los bytes firmados, la comparación, la gestión de claves y la protección contra replay. Con una especificación clara, HMAC es robusto para webhooks y comunicación entre servicios.

Consulta la documentación oficial de hmac y el RFC 2104.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Lector de huella digital que representa verificación de hashes con hashlib en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    hashlib en Python: hashes seguros

    Aprende hashlib en Python para calcular SHA-256, verificar archivos, usar BLAKE2, derivar claves y evitar errores comunes de seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    19/08/2026
    Código binario proyectado que representa conversiones con binascii en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    binascii en Python: binario y ASCII

    Aprende binascii en Python para convertir hexadecimal, Base64 y quoted-printable, calcular CRC y validar datos binarios de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    18/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    codecs en Python: domina encodings

    Aprende codecs en Python para usar encodings, handlers, BOM, streams incrementales y migrar codecs.open a open.

    Ler mais

    Tempo de leitura: 6 minutos
    18/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    base64 en Python: codifica datos

    Aprende base64 en Python para codificar bytes, usar Base64 URL-safe, validar padding, aplicar límites y diferenciar encoding de cifrado.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Detailed image of a Burmese Python being held. Captured in Toluca, Mexico.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.parse en Python: maneja URLs

    Aprende urllib.parse en Python para dividir URLs, crear queries, codificar componentes y evitar riesgos con urljoin, redirects, logs y SSRF.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ipaddress en Python: redes IPv4 e IPv6

    Aprende ipaddress en Python para validar IPv4 e IPv6, calcular redes CIDR, dividir subredes y crear políticas de acceso más

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026