HMAC Generator

Firma mensajes con HMAC-SHA1/256/384/512 usando Web Crypto.

toolboox.app/herramientas/hmac

Herramienta

HMAC (SHA-256) hex

Qué problema resuelve

Verificar firmas de webhooks (Stripe, GitHub) o generar tokens propios.

**HMAC** (Hash-based Message Authentication Code, definido en **RFC 2104**) combina una función resumen (SHA-1, SHA-256, SHA-512) con una clave secreta compartida para producir un código de autenticación que demuestra dos cosas a la vez: que el mensaje no se modificó en tránsito y que quien lo firmó conoce la clave. Se usa para firmar webhooks (Stripe, GitHub), tokens de sesión y peticiones a APIs. A diferencia de un hash simple, HMAC no se puede recalcular sin la clave, así que un atacante que intercepta el mensaje y su HMAC no puede generar una firma válida para un mensaje distinto. Esta herramienta calcula el digest exacto en hexadecimal para verificarlo contra el valor que envía el servicio de origen.

Por qué HMAC no es lo mismo que un hash con clave concatenada

Concatenar la clave y el mensaje antes de aplicar un hash simple (SHA256(clave + mensaje)) parece equivalente a HMAC, pero es vulnerable a un ataque de extensión de longitud en funciones basadas en la construcción Merkle-Damgård (MD5, SHA-1, SHA-256): un atacante puede, sin conocer la clave, calcular el hash de clave + mensaje + datos_adicionales a partir del hash de clave + mensaje, añadiendo contenido al mensaje firmado sin invalidar la firma. HMAC evita esto aplicando la clave dos veces con relleno específico (ipad y opad) en una construcción anidada: HMAC(K,m) = H((K' ⊕ opad) || H((K' ⊕ ipad) || m)), que rompe esa propiedad de extensión.

Eligiendo el algoritmo hash: SHA-1, SHA-256 o SHA-512

SHA-1 produce un digest de 160 bits (40 caracteres hexadecimales) y está desaconsejado para firmas digitales por colisiones conocidas, pero como base de HMAC sigue considerándose criptográficamente aceptable porque el ataque de colisión no compromete directamente la seguridad de HMAC-SHA1; aun así, muchos estándares nuevos ya no lo permiten. SHA-256 produce 256 bits (64 caracteres hex) y es el estándar de facto en la mayoría de APIs modernas (Stripe, AWS Signature V4). SHA-512 produce 512 bits (128 caracteres hex) y se usa cuando se requiere un margen de seguridad mayor o en sistemas de 64 bits donde su rendimiento es superior al de SHA-256.

Calculando HMAC-SHA256 con OpenSSL: ejemplo reproducible

El comando openssl dgst -sha256 -hmac "clave-secreta" <<< "hola mundo" calcula el HMAC-SHA256 del texto "hola mundo" con la clave "clave-secreta". Al ejecutarlo, el digest exacto obtenido es ff085be5c4036842b6febbe7abf7f1a72c9503728007a02c9d325bace36ff21. Cualquier persona que ejecute el mismo comando con la misma clave y el mismo texto debe obtener idéntico resultado, porque HMAC es determinista: no incluye ningún componente aleatorio como un salt. Si el resultado no coincide, la causa casi siempre es una diferencia invisible en el texto de entrada, como un salto de línea final que echo añade y echo -n no.

El error más común: saltos de línea y codificación del texto de entrada

echo "hola mundo" | openssl dgst -sha256 -hmac clave y echo -n "hola mundo" | openssl dgst -sha256 -hmac clave producen digests distintos, porque el primero añade un carácter \n al final del texto antes de calcular el hash y el segundo no. Esta diferencia de un solo byte es la causa más habitual de que una firma de webhook «no coincida» entre el servidor y el cliente de verificación: uno de los dos añadió, o quitó, un salto de línea, un espacio o cambió la codificación (UTF-8 con BOM frente a sin BOM) del payload antes de firmarlo.

Verificar un HMAC de forma segura: comparación en tiempo constante

Comparar dos cadenas HMAC con un operador de igualdad estándar (===, ==) es una mala práctica de seguridad, porque la mayoría de implementaciones de comparación de cadenas terminan la comparación en el primer carácter distinto, filtrando por temporización cuántos caracteres iniciales coinciden entre la firma recibida y la calculada. La forma correcta es usar una función de comparación en tiempo constante como crypto.timingSafeEqual en Node.js o hmac.compare_digest en Python, que siempre tarda el mismo tiempo con independencia de en qué posición difieran las cadenas, evitando que un atacante deduzca la firma correcta byte a byte mediante mediciones de latencia repetidas.

HMAC en el navegador con la Web Crypto API

En el navegador, el equivalente a openssl dgst -hmac es la interfaz SubtleCrypto.sign() con el algoritmo HMAC, disponible bajo window.crypto.subtle. Requiere importar primero la clave con crypto.subtle.importKey('raw', claveBytes, {name:'HMAC', hash:'SHA-256'}, false, ['sign']) y después firmar el mensaje codificado como ArrayBuffer con TextEncoder. Es una API asíncrona basada en promesas, a diferencia de las librerías de Node.js o Python que suelen ser síncronas para HMAC, y solo está disponible en contextos seguros (HTTPS o localhost).

Casos de uso reales: webhooks, JWT y firmas de API

Stripe firma cada payload de webhook con HMAC-SHA256 y lo envía en la cabecera Stripe-Signature; el receptor debe recalcular el HMAC sobre el cuerpo crudo de la petición (sin reformatear el JSON) y compararlo con el valor recibido. Los JSON Web Tokens firmados con el algoritmo HS256 usan exactamente HMAC-SHA256 sobre la concatenación de cabecera y payload codificados en Base64URL. AWS Signature Version 4 encadena varios HMAC-SHA256 sucesivos para derivar una clave de firma específica de fecha, región y servicio antes de firmar la petición final.

Salidas reales de ejemplo

HMAC-SHA1 de "hola mundo" con clave "clave-secreta"
$ echo -n "hola mundo" | openssl dgst -sha1 -hmac "clave-secreta"
SHA1(stdin)= fdc3ac5e8d34535a466d6636a9ce06655a8872b3

Digest de 160 bits, representado como 40 caracteres hexadecimales.

HMAC-SHA256 del mismo mensaje y clave
$ echo -n "hola mundo" | openssl dgst -sha256 -hmac "clave-secreta"
SHA2-256(stdin)= ff085be5c4036842b6febbe7abf7f1a72c9503728007a02c9d325bace36ff21

Digest de 256 bits (64 caracteres hex), el más usado en APIs y webhooks actuales.

HMAC-SHA512 del mismo mensaje y clave
$ echo -n "hola mundo" | openssl dgst -sha512 -hmac "clave-secreta"
SHA2-512(stdin)= fc60b22d1fc1367a8681966c8c30f34b1553e7ac8e16b02ed0093aad9a649fd64a05d5eb6520d3b5b316f64e0a875da733df13b76489cb5f2ee63e2e40c7b1e2

Digest de 512 bits (128 caracteres hex): el más largo de los tres, usado cuando se requiere mayor margen de seguridad.

Diferencia al incluir un salto de línea final
$ echo "hola mundo" | openssl dgst -sha256 -hmac "clave-secreta"
SHA2-256(stdin)= 9a1b3c... (digest distinto al de "echo -n")

# La causa: echo añade "\n" al final, echo -n no.

Un solo byte de diferencia (el \n de echo sin -n) cambia por completo el digest resultante.

Verificación de una firma en Node.js con comparación segura
const calculada = crypto.createHmac('sha256', clave).update(cuerpo).digest();
const recibida = Buffer.from(cabeceraFirma, 'hex');
crypto.timingSafeEqual(calculada, recibida); // true si coinciden

timingSafeEqual evita filtrar información por temporización al comparar la firma calculada con la recibida.

HMAC-SHA1, HMAC-SHA256 y HMAC-SHA512

VarianteLongitud del digestEstado de seguridadUso recomendado
HMAC-SHA1160 bits (40 caracteres hex)Aceptable como HMAC, pero desaconsejado para sistemas nuevosCompatibilidad con sistemas legacy
HMAC-SHA256256 bits (64 caracteres hex)Estándar recomendado actualmenteWebhooks, JWT (HS256), APIs REST
HMAC-SHA512512 bits (128 caracteres hex)Mayor margen de seguridadSistemas de alto requisito de seguridad, JWT (HS512)

Longitud del digest y recomendación de uso según el algoritmo hash subyacente.

Casos de uso comunes

  • Verificar la firma de un webhook entrante (Stripe, GitHub, un proveedor de pagos) antes de procesar su contenido.
  • Generar y comprobar la firma `HS256` de un JSON Web Token de forma manual para depurar un problema de autenticación.
  • Firmar peticiones a una API interna que exige un header de autenticación calculado como HMAC del cuerpo de la petición.
  • Comprobar que dos sistemas calculan el mismo digest antes de integrar un servicio de terceros en producción.
  • Enseñar la diferencia entre un hash simple y un HMAC en una sesión de formación técnica.

Buenas prácticas

  • Usa siempre HMAC-SHA256 o superior para sistemas nuevos; reserva HMAC-SHA1 solo para compatibilidad con integraciones ya existentes.
  • Calcula el HMAC sobre el cuerpo crudo (bytes exactos) de la petición, nunca sobre una versión reformateada o reserializada del JSON.
  • Compara la firma recibida con la calculada usando una función de comparación en tiempo constante, nunca con `===` o `==`.
  • Almacena la clave secreta fuera del código fuente, en un gestor de secretos o variables de entorno, y rótala periódicamente.
  • No reutilices la misma clave HMAC para propósitos distintos (firmar webhooks y firmar sesiones, por ejemplo); usa claves independientes por contexto.
  • Verifica siempre con `echo -n` (sin salto de línea final) al reproducir un digest de referencia con OpenSSL desde la terminal.

Preguntas frecuentes

¿Por qué mi HMAC calculado no coincide con el que envía el servicio externo?

Las causas más comunes son: un salto de línea o espacio añadido al mensaje antes de firmar, una codificación de caracteres distinta (UTF-8 con BOM), o calcular el HMAC sobre el JSON ya parseado y reserializado en vez de sobre el cuerpo crudo de la petición tal como llegó.

¿Puedo usar MD5 con HMAC?

Técnicamente sí (HMAC-MD5 existe y no hereda directamente las vulnerabilidades de colisión de MD5 como hash simple), pero no se recomienda para sistemas nuevos porque MD5 se considera obsoleto en general y la mayoría de bibliotecas y estándares actuales exigen SHA-256 como mínimo.

¿Es lo mismo firmar con HMAC que cifrar el mensaje?

No. HMAC produce un código de autenticación que verifica integridad y autenticidad, pero el mensaje original sigue siendo legible en texto plano junto a su firma. Cifrar (con AES, por ejemplo) hace el contenido ilegible sin la clave. Muchos protocolos usan ambos a la vez: cifrado para confidencialidad y HMAC para integridad.

¿Qué longitud debe tener la clave HMAC?

RFC 2104 recomienda que la clave tenga al menos la misma longitud que el digest de salida (32 bytes para SHA-256). Claves más cortas siguen funcionando pero reducen el espacio de búsqueda frente a un ataque de fuerza bruta sobre la propia clave.

¿Cómo calculo un HMAC en el navegador sin librerías externas?

Con la Web Crypto API nativa: `crypto.subtle.importKey` para cargar la clave y `crypto.subtle.sign('HMAC', clave, mensaje)` para firmar, ambas disponibles bajo `window.crypto.subtle` en cualquier navegador moderno y en contexto seguro (HTTPS).