En una frase
HMAC es un saludo criptográfico que usa una clave secreta compartida para demostrar que un mensaje es auténtico y que nadie lo ha toqueteado.
El problema que soluciona
Allá por los inicios del internet, en sus días del lejano oeste, enviar un mensaje era como mandar una postal. Cualquiera que lo interceptara podía leerlo, y quizás hasta garabatearle algo antes de que siguiera su camino. Si recibías una postal que decía "Nos vemos a medianoche, trae el dinero", ¿cómo podías estar seguro de que realmente venía de tu contacto supersecreto y no de su némesis, la Malvada Eva? ¿Y cómo estar seguro de que originalmente no decía "Nos vemos al mediodía para un almuerzo amistoso"?
Este es el doble problema de la autenticidad (¿realmente viene de ti?) y la integridad (¿ha sido modificado?).
Una función de hash criptográfica simple (como SHA-256) parece un buen primer paso. Podrías hashear tu mensaje, enviar el mensaje y el hash, y el receptor podría volver a hashear el mensaje para ver si coinciden. ¡Genial! Eso soluciona la integridad. Si un solo byte del mensaje cambiara, los hashes no coincidirían.
Pero eso no soluciona la autenticidad. La Malvada Eva simplemente puede cambiar el mensaje, calcular un nuevo hash para su nuevo mensaje y enviar ambos. El receptor verá que el hash coincide con el mensaje, pero no tiene forma de saber que el paquete completo es una falsificación.
Aquí es donde HMAC (Hash-based Message Authentication Code) entra en escena. Al introducir una clave secreta compartida en el proceso de hashing, HMAC crea una firma que solo alguien con esa clave secreta puede producir. Es la diferencia entre un simple sello de cera (cualquiera puede hacer uno) y un sello de cera hecho con un anillo de sello único (solo el rey tiene uno). HMAC nos da tanto integridad como autenticidad.
Cómo funciona por debajo
HMAC no es un nuevo tipo de función de hash; es una receta que usa funciones de hash existentes (como SHA-256) de una manera ingeniosa y específica. La especificación oficial es el RFC 2104, pero vamos a desglosarlo en español simple.
Los Ingredientes
Para cocinar un HMAC, necesitas tres cosas:
- El Mensaje: Los datos que quieres proteger. Esto podría ser un payload JSON para un webhook, una cadena de parámetros de URL o cualquier trozo de datos binarios.
- La Clave Secreta: Una cadena de bytes conocida solo por el emisor y el receptor. Este es el ingrediente mágico. Si se ve comprometida, todo el sistema se rompe.
- La Función de Hash: Un algoritmo estándar como SHA-1, SHA-256 o SHA-512. La elección de la función de hash determina la longitud de la firma HMAC final (por ejemplo, HMAC-SHA256 produce una firma de 256 bits).
La Receta (El Algoritmo HMAC)
Podrías pensar que bastaría con hacer hash(key + message). Parece simple, pero esta construcción es vulnerable a unas triquiñuelas criptográficas ingeniosas llamadas "ataques de extensión de longitud" (length extension attacks). La construcción oficial de HMAC es un poco más compleja, específicamente para prevenir estos ataques. Usa un proceso de doble hashing.
Aquí tienes un vistazo simplificado de los pasos:
Prepara la Clave: La función de hash opera sobre bloques de datos de tamaño fijo (p. ej., 64 bytes para SHA-256). La clave debe prepararse para ajustarse a este tamaño de bloque.
- Si la clave es más larga que el tamaño del bloque, hasheas la propia clave y usas ese resultado como la nueva clave.
- Si la clave es más corta que el tamaño del bloque, la rellenas con bytes cero hasta que alcance el tamaño del bloque.
Crea Claves Interna y Externa: A partir de esta clave preparada, derivamos dos claves distintas.
ipad(inner pad): un byte constante (0x36) repetido para rellenar el tamaño del bloque.opad(outer pad): un byte constante diferente (0x5C) repetido para rellenar el tamaño del bloque.
Creamos una
inner_padded_keytomando nuestra clave preparada y aplicándole un XOR conipad. Creamos unaouter_padded_keyaplicándole un XOR a la clave preparada conopad.Realiza el Doble Hash: Ahora, el evento principal.
- Hash Interno: Concatena la
inner_padded_keycon el mensaje original, y pásalo por la función de hash. - Hash Externo: Concatena la
outer_padded_keycon el resultado del hash interno, y pásalo por la función de hash.
- Hash Interno: Concatena la
¡El resultado final de este hash externo es tu firma HMAC!
En pseudocódigo, se ve así:
function hmac(key, message, hash_function, block_size) {
// 1. Prepare the key
if (key.length > block_size) {
key = hash_function(key);
}
if (key.length < block_size) {
key = pad_with_zeros(key, block_size);
}
// 2. Create inner and outer padded keys
o_key_pad = key XOR (0x5C repeated to block_size);
i_key_pad = key XOR (0x36 repeated to block_size);
// 3. Perform the double hash
inner_hash_result = hash_function(i_key_pad + message);
final_hmac = hash_function(o_key_pad + inner_hash_result);
return final_hmac;
}
¿Por qué el Doble Hash?
Esta estructura de interno-luego-externo es el ingrediente secreto. El hash interno combina el secreto y el mensaje. El hash externo básicamente hashea de nuevo el resultado de la primera operación, junto con el secreto. Esto "sella" el hash interno. Hace que sea computacionalmente imposible para un atacante manipular el resultado del hash intermedio sin conocer la clave, frustrando así los ataques de extensión de longitud y otras posibles vulnerabilidades criptográficas. Es una construcción robusta y probada que ha resistido el paso del tiempo.
Historias del mundo real
El Guardián de Webhooks de GitHub
El equipo de una startup tenía su servidor de integración continua (CI) configurado para desplegar automáticamente su app a producción cada vez que alguien hacía push a la rama main. El disparador era un webhook de GitHub: una petición POST enviada desde los servidores de GitHub a una URL pública en su servidor de CI. Una noche, su becario bromista encontró la URL pública y, usando un simple comando cURL, empezó a enviar payloads de webhooks falsos, disparando docenas de despliegues inútiles que consumían un montón de recursos.
La desarrolladora senior lo arregló en 15 minutos. En la configuración de webhooks de GitHub, generó un "secreto" largo y aleatorio. Copió este secreto y lo configuró como una variable de entorno en su servidor de CI. Ahora GitHub usaba este secreto para generar una firma HMAC-SHA256 para cada payload del webhook, enviándola en un header X-Hub-Signature-256. El código del servidor de CI fue actualizado para realizar el mismo cálculo HMAC sobre el cuerpo crudo de la petición que recibía, usando el mismo secreto. Si la firma que calculaba coincidía con la del header, la petición se procesaba. Si no, era rechazada con un 403 Forbidden. Las bromas cesaron de inmediato.
Lección: Siempre asegura tus webhooks con verificación de firma HMAC. No te fíes de ninguna petición entrante hasta que haya sido autenticada.
Asegurando el Tarro de Galletas de la API
Un desarrollador estaba construyendo un servicio que usaba una cookie simple y firmada para la autenticación. Cuando un usuario iniciaba sesión, el servidor emitía una cookie que contenía su user_id y un timestamp de expiry (caducidad). Para evitar que los usuarios editaran su cookie para convertirse en otro usuario (p. ej., cambiando user_id=123 a user_id=1), el desarrollador incluyó una firma HMAC.
El payload de la cookie se veía algo así: user_id=123&expiry=1678886400. El servidor firmaba esta cadena exacta con una clave secreta guardada en el servidor. La cookie final enviada al navegador era data="user_id=123&expiry=1678886400"&signature="sha1=2a8b...".
Cuando el usuario hacía una petición posterior, su navegador enviaba la cookie de vuelta. El servidor tomaría la parte data, recalcularía la firma HMAC usando su clave secreta, y la compararía con la parte signature de la cookie. Si coincidían, el servidor sabía que el user ID y la fecha de caducidad eran legítimos y no habían sido manipulados.
Lección: HMAC es una forma fantástica de crear tokens o cookies "stateless" (sin estado) y a prueba de manipulaciones, formando la base de muchos sistemas de autenticación, incluyendo los JSON Web Tokens (JWTs).
La Transferencia Bancaria que no fue Secuestrada
Una plataforma de e-commerce se integró con la API de un procesador de pagos para iniciar pagos a sus vendedores. La llamada a la API era un mensaje JSON simple: {"vendor_id": "ven_abc", "amount": 500.00, "currency": "USD"}. A la plataforma le preocupaba un ataque de tipo man-in-the-middle (MITM). Incluso sobre HTTPS, que cifra el tráfico, un atacante sofisticado podría (en algunos escenarios teóricos, como con una autoridad de certificación comprometida) interceptar y modificar la petición. Podrían cambiar el amount a 50000.00 o el vendor_id por el suyo propio.
La API del procesador de pagos requería que cada petición estuviera firmada con HMAC-SHA512. La plataforma serializaba el payload JSON a una cadena canónica, calculaba la firma con su clave de API privada y la enviaba en un header Authorization. Los servidores del procesador de pagos realizaban exactamente los mismos pasos. Si su firma calculada coincidía con la enviada en el header, sabían dos cosas con certeza: la petición provenía de la plataforma legítima (autenticidad) y que el vendor_id y el amount no habían sido alterados en el camino (integridad).
Lección: Para operaciones de alto riesgo, HMAC proporciona una capa de seguridad crítica para garantizar que el remitente y el contenido de un mensaje son los que esperas.
Errores y trampas comunes
- Filtrar la clave secreta. La clave lo es todo. Si la expones en el JavaScript del lado del cliente, la commiteas a un repositorio público de Git, o la guardas en un log en texto plano, tu seguridad se fue al traste. Trátala como una contraseña.
- Usar una comparación que no es de tiempo constante. Cuando verificas si la firma proporcionada por el usuario coincide con la que calculaste, una comparación de cadenas estándar como
if (a === b)puede ser un agujero de seguridad. A menudo devuelvefalsetan pronto como encuentra un carácter que no coincide. Esto crea una diminuta diferencia de tiempo que los atacantes pueden medir para adivinar la firma, un caracter a la vez. Esto es un "ataque de tiempo" (timing attack). Usa siempre una función de comparación dedicada, de "tiempo constante", de una librería de criptografía, que tarde la misma cantidad de tiempo sin importar dónde ocurra la primera diferencia. - Firmar los datos incorrectos. El emisor y el receptor deben calcular el HMAC sobre la misma secuencia exacta de bytes. Un bug común es que un lado firme un objeto JSON formateado para ser legible ("prettified") mientras que el otro firma la versión compacta de una sola línea. O que un lado incluya un salto de línea al final y el otro no. Deben ponerse de acuerdo en un formato de mensaje canónico y apegarse a él.
- Olvidarse de los ataques de repetición (replay attacks). HMAC por sí solo no impide que un atacante capture un mensaje válido y firmado y simplemente lo reenvíe una y otra vez. Si ese mensaje es "págale a Bob $10", no quieres que el atacante pueda disparar ese pago 1000 veces. Para evitar esto, incluye un valor que cambie con cada petición —como un timestamp o un "nonce" (number used once / número usado una sola vez)— dentro de los datos que se firman. El servidor puede entonces verificar el timestamp para rechazar peticiones antiguas o mantener una lista de nonces ya usados para rechazar duplicados.
Por qué debería estar en tu radar
Deberías pensar en HMAC siempre que estés lidiando con comunicación que necesite ser confiable. No se trata de mantener los datos en secreto (ese es el trabajo del cifrado), sino de asegurar que los datos son legítimos.
- ¿Construyendo o consumiendo APIs? Especialmente con webhooks (de servicios como Stripe, GitHub, Twilio), HMAC es el estándar de la industria para verificar que la petición es auténtica.
- ¿Trabajando con autenticación? Muchos sistemas basados en tokens, de los cuales los JWTs son los más famosos, usan HMAC (p. ej., el algoritmo 'HS256') para firmar el payload del token, evitando que los usuarios modifiquen sus propios permisos.
- ¿Necesitas verificar la integridad de los datos? Si estás pasando datos a través de un entorno no confiable (como el navegador de un usuario en una cookie) y necesitas asegurarte de que regresen sin modificar, HMAC es tu herramienta.
Es una primitiva fundamental en la caja de herramientas de seguridad de un desarrollador web. Entender cómo funciona te convertirá en un ingeniero mejor y más consciente de la seguridad.
Para profundizar
- RFC 2104: HMAC: Keyed-Hashing for Message Authentication: La especificación técnica original. Es densa, pero es la fuente de la verdad absoluta.
- Wikipedia: HMAC: Un excelente resumen de alto nivel de la historia, los principios de diseño y los detalles de implementación.
- OWASP: Replay Attack: La página del Open Web Application Security Project sobre ataques de repetición, un concepto crítico para entender al usar HMAC.
- Stripe Docs: Checking webhook signatures: Una guía práctica y del mundo real de una compañía que depende fuertemente de HMAC para asegurar miles de millones de dólares en transacciones.
- Crypto 101: HMAC: Una explicación un poco más accesible de los principios criptográficos detrás del diseño de HMAC.