HMAC frente a firma digital es la decisión de diseño más importante de cualquier esquema de autenticación de mensajes, y es una cuestión de confianza, no de velocidad. Una etiqueta HMAC se calcula y se comprueba con la misma clave secreta, así que cualquiera que pueda verificarla también puede falsificarla. Una firma se crea con una clave privada y se comprueba con una clave pública, así que los verificadores no pueden crear nuevos mensajes válidos.
De un vistazo
| HMAC (RFC 2104, FIPS 198-1) | Firma digital | |
|---|---|---|
| Claves | Un único secreto compartido | La clave privada firma y la clave pública verifica |
| Quién puede crear una etiqueta o una firma válida | Cualquiera que tenga el secreto, incluido cada verificador | Solo el titular de la clave privada |
| No repudio (demostrable ante terceros) | No | Sí, si existe una vinculación de confianza entre la clave y el firmante |
| Distribución de claves | El secreto debe llegar de forma confidencial a cada verificador | La clave pública se puede publicar (JWKS, certificado, DNS) |
| Tamaño de la prueba | 32 bytes con HMAC-SHA256 | 64 bytes (Ed25519, ECDSA P-256), 256–512 bytes (RSA) |
| Velocidad, un núcleo de Apple M4 Max | unos 7 millones de etiquetas por segundo con mensajes de 256 bytes (OpenSSL 3.6.5) | unas 60.000 firmas Ed25519 o 87.000 firmas P-256 por segundo |
| Usos típicos | Webhooks, cookies de sesión, AWS SigV4, TOTP, JWT HS256, MAC de registros de TLS 1.2 | Certificados y handshakes TLS, tokens de ID de OIDC, firma de paquetes, Git, llaves de acceso |
| Seguridad poscuántica | Seguro con SHA-256 (Grover solo reduce a la mitad el esfuerzo para hallar una preimagen) | Shor rompe los esquemas clásicos; hace falta ML-DSA o algo similar |
El rendimiento de HMAC se midió con openssl speed -hmac sha256 (unos 1,83 GB/s con entradas de 256 bytes); las velocidades de firma, con openssl speed.
La diferencia está en el modelo de confianza
Un HMAC es un hash con clave: H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). Su objetivo de seguridad es que nadie sin la clave pueda producir una etiqueta válida. Eso basta cuando exactamente dos partes comparten la clave y confían en que ninguna de las dos falsificará etiquetas, porque entonces una etiqueta válida significa «esto lo envió la otra parte». Con tres o más partes el modelo deja de funcionar: cualquier verificador puede hacerse pasar por el remitente ante los demás y, en una disputa, el remitente puede alegar que la etiqueta la creó el receptor.
Una firma digital separa las capacidades. Publicar la clave de verificación no ayuda a nadie a firmar, así que un solo emisor puede atender a miles de verificadores, y una firma puede presentarse ante un auditor o un tribunal como prueba de origen, siempre que la clave estuviera vinculada al firmante mediante un certificado, un directorio de claves o un registro de cuenta. Esa separación explica por qué los tokens de identidad que consumen muchos servicios (los tokens de ID de OpenID Connect, los tokens de acceso que comprueban los servidores de recursos) usan firmas, y por qué una configuración de verificador filtrada resulta inofensiva con firmas, pero catastrófica con HMAC.
Aspectos prácticos
Gestión de claves. Los secretos HMAC deben generarse de forma aleatoria (al menos 256 bits para HMAC-SHA256; el RFC 7518 §3.2 lo exige para HS256), transmitirse de forma confidencial, almacenarse en cada verificador y rotarse en todas partes a la vez o con un periodo de gracia en el que hay dos secretos válidos. Con las claves de firma, la mitad privada se guarda en un solo lugar, idealmente un HSM o un KMS, y la rotación se hace mediante identificadores de clave en un conjunto de claves publicado.
Rendimiento y tamaño. HMAC consiste en dos cálculos de hash: millones por segundo y por núcleo, con una etiqueta de 32 bytes. Las firmas cuestan decenas de microsegundos y ocupan 64 bytes o más. Nada de esto importa para la mayor parte del tráfico web; sí importa en la autenticación por paquete o en la mensajería interna de muy alto volumen, donde lo habitual es HMAC o un AEAD.
Errores habituales. El HMAC debe compararse en tiempo constante, no debe sustituirse por hash(key ‖ message) (una construcción que la extensión de longitud de SHA-256 rompe) y debe autenticar exactamente los bytes recibidos (el cuerpo de la solicitud en bruto, no un objeto JSON vuelto a serializar). Las firmas deben verificarse con un algoritmo que fije el verificador, nunca con uno que elija el mensaje.
El script siguiente, ejecutado con Node.js 26.10, autentica el mismo cuerpo de webhook de las dos formas.
import { createHmac, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
const body = Buffer.from('{"event":"invoice.paid","id":"evt_123"}');
// HMAC: sender and receiver share one secret (in production it comes from a secret store).
const secret = randomBytes(32);
const tag = createHmac("sha256", secret).update(body).digest();
const expected = createHmac("sha256", secret).update(body).digest();
console.log("HMAC-SHA256 tag:", tag.length, "bytes, valid:", timingSafeEqual(tag, expected));
// Signature: sender keeps the private key, receivers hold only the public key.
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
const signature = sign(null, body, privateKey);
console.log("Ed25519 signature:", signature.length, "bytes, valid:", verify(null, body, publicKey, signature));
HMAC-SHA256 tag: 32 bytes, valid: true
Ed25519 signature: 64 bytes, valid: true
El secreto HMAC tiene que compartirse con cada verificador; la clave privada Ed25519 nunca sale del remitente, y solo se distribuye la clave pública de 32 bytes.
Webhooks y API en la práctica
La mayoría de los proveedores de webhooks usan HMAC porque cada suscriptor recibe su propio secreto: GitHub envía X-Hub-Signature-256: sha256= seguido del HMAC-SHA256 del cuerpo en hexadecimal, y Stripe firma una marca de tiempo junto con el cuerpo en su encabezado Stripe-Signature para que los receptores puedan rechazar los ataques de repetición. Como el secreto de cada suscriptor es único, el problema de las múltiples partes no se plantea. Los webhooks firmados, basados en HTTP Message Signatures (RFC 9421) o en JWS, aparecen cuando un remitente se comunica con muchos receptores que no deberían compartir un secreto, o cuando los receptores quieren verificar sin tener que aprovisionar secretos. AWS Signature Version 4 es una firma de solicitudes basada en HMAC; OAuth DPoP y Web Bot Auth usan firmas porque el verificador no debe poder hacerse pasar por el cliente.
Cuándo elegir cada uno
- El mismo servicio emite y verifica (cookies de sesión, tokens CSRF, JWT internos): HMAC-SHA256.
- Un remitente, un receptor y un secreto compartido fuera de banda (webhooks por cliente): HMAC con una marca de tiempo en los datos firmados.
- Un emisor, muchos verificadores (tokens de identidad, API públicas, aplicaciones móviles que verifican datos del servidor): firmas, con las claves publicadas en un JWKS.
- Pruebas para terceros (contratos, registros de auditoría, versiones de software): firmas más sellos de tiempo de confianza.
- Clientes que no deben poder hacerse pasar unos por otros (DPoP, autenticación de bots, atestación de dispositivos): firmas con claves propias de cada cliente.
- Volúmenes de mensajes muy altos en un enlace de confianza: HMAC o un AEAD.
Notas de migración
El paso de HMAC a firmas suele llegar cuando aparece un segundo verificador o se filtra un secreto.
- Introduce un identificador de algoritmo si tu formato no lo tiene (el
algde JOSE, un parámetroalgde la firma HTTP, un prefijo de versión), para que los verificadores sepan qué esquema usa un mensaje sin fiarse ciegamente de él. - Publica la clave pública con un identificador de clave (JWKS, directorio de claves de firmas HTTP) y da tiempo a los verificadores para obtenerla.
- Firma por partida doble durante la transición: envía tanto el encabezado HMAC como la firma, y deja que los verificadores prefieran la firma.
- Elimina el secreto compartido de los verificadores que solo necesitan verificar; esa es la mejora de seguridad, así que no lo conserves «por si acaso».
- No dejes nunca que una misma clave sirva para los dos esquemas. Una clave pública RSA reutilizada como secreto HMAC es exactamente la confusión de algoritmos que rompió bibliotecas JWT; consulta HS256 frente a RS256.
Referencias
- RFC 2104, HMAC
- FIPS 198-1, el código de autenticación de mensajes basado en hash con clave
- FIPS 186-5, estándar de firma digital
- RFC 7518 §3.2, HMAC con SHA-2 en JWS
- RFC 9421, firmas de mensajes HTTP
- GitHub: validación de las entregas de webhooks
- Stripe: gestión de endpoints de webhooks y verificación de firmas