CAPÍTULO 64 · PARTE VI

MAC, autenticidad e integridad con clave

Define la garantía de un MAC bajo clave compartida, explica HMAC y KMAC, y separa tag válido de confidencialidad, frescura, replay, autorización y atribución.

Nivel N2 · Estado published

Un digest público permite comprobar que unos bytes coinciden con una referencia. No permite saber quién calculó esa referencia: cualquiera puede hashear su propia modificación. Un message authentication code cambia el contrato al introducir una clave secreta compartida. Quien no posee la clave debería ser incapaz de producir un tag válido para un mensaje nuevo, aun después de observar o solicitar tags de otros mensajes.

Esa garantía sigue siendo precisa y limitada. Un MAC no cifra, no impide replay, no autoriza una operación y no distingue cuál de varios poseedores de la clave generó el tag. La palabra «autenticado» sólo es segura cuando se completa: autenticado por cuál clave, para qué bytes, contexto, tiempo y receptor.

El contrato mínimo de un MAC

Un MAC tiene tres elementos conceptuales:

El emisor y el verificador comparten K o material derivado equivalente. Un atacante puede observar pares mensaje–tag y, en un modelo fuerte, elegir mensajes para obtener sus tags. La propiedad buscada es que no pueda crear un par nuevo que el verificador acepte. Esta idea se formaliza como resistencia a existential forgery under chosen-message attack: el adversario no necesita recuperar la clave ni falsificar un mensaje específico; cualquier falsificación nueva cuenta.

El tag suele viajar junto al mensaje y puede ser público. La clave no. Ocultar el tag no es la fuente de seguridad. Tampoco se requiere que dos tags de mensajes parecidos se parezcan o no: la propiedad relevante es la dificultad de falsificación bajo el mecanismo completo.

Si Verify acepta, puede concluirse que alguien con acceso a la clave produjo el tag para esos bytes y contexto, o que ocurrió una falsificación dentro del riesgo aceptado. No puede concluirse qué persona fue, que el contenido sea verdadero ni que el receptor deba ejecutar la acción.

Autenticidad simétrica y sus fronteras

En criptografía simétrica, todos los poseedores de K pueden generar tags. Si frontend, backend y batch comparten la misma clave, cualquiera puede impersonar a cualquiera frente a los demás. Los logs no convierten esa simetría en no repudio.

Para atribución por emisor se usan claves separadas por relación o mecanismos asimétricos. Aun una firma se atribuye a una clave, no automáticamente a una intención humana; el capítulo 67 desarrolla esa frontera.

Un MAC aporta integridad intencional frente a quien no conoce K: modificar bytes sin recalcular el tag debería detectarse. No aporta confidencialidad; mensaje, headers y metadata siguen visibles. Cifrar sin autenticar y autenticar sin cifrar son contratos distintos. AEAD, en el capítulo 65, combina propiedades bajo una interfaz y requisitos propios.

Un tag válido tampoco concede autoridad. Si el mensaje dice role=admin, el MAC prueba que un poseedor de K autenticó esa cadena. El receptor aún debe comprobar que ese emisor puede conceder roles, que el sujeto y tenant son correctos y que la transición está permitida.

HMAC: una construcción, no una concatenación

FIPS 198-1 y RFC 2104 definen HMAC sobre una función hash iterativa. En notación compacta:

HMAC(K, M) = H((K₀ xor opad) || H((K₀ xor ipad) || M))

K₀ es la clave normalizada al block size de la función: una clave demasiado larga se hashea y luego se rellena; una corta se rellena. ipad y opad son constantes distintas repetidas al block size.

La doble estructura separa el procesamiento interno y externo. No debe simplificarse como «hash con secreto». H(K || M), H(M || K) y HMAC son construcciones diferentes. La primera puede ser vulnerable a length extension con hashes Merkle–Damgård; otras composiciones pueden fallar bajo análisis distintos.

HMAC fue diseñado para reutilizar funciones hash mediante una construcción analizada. Que una función tenga colisiones prácticas no implica automáticamente el mismo ataque contra HMAC, pero tampoco justifica elegir algoritmos heredados. RFC 6151, por ejemplo, actualiza el riesgo de MD5 y permite usos limitados de HMAC-MD5 mientras desaconseja su adopción en protocolos nuevos. La decisión actual debe seguir estándares y perfiles vigentes, no una inferencia general.

La selección incluye función hash, tag length y key strength. «HMAC» sin parámetros no es un algoritmo interoperable. HMAC-SHA-256 con 128 bits truncados y HMAC-SHA-512 completo tienen contratos diferentes.

KMAC no es HMAC con SHA-3

SP 800-185 define KMAC128 y KMAC256 sobre KECCAK/cSHAKE. KMAC acepta clave, mensaje, longitud de salida y customization string. No es HMAC aplicado mecánicamente a SHA-3 ni produce el mismo tag.

La customization string puede separar protocolos y propósitos: payments-webhook-v1 y audit-record-v1 ocupan dominios diferentes aun con la misma primitiva. Esto ayuda a prevenir reinterpretación, pero no reemplaza separación de claves cuando el impacto o autoridad difiere.

KMAC soporta salida variable. Pedir más bits no supera la security strength de KMAC128/KMAC256 ni la fuerza de la clave. Pedir pocos bits reduce la resistencia a guesses online. SP 800-185 está marcado para revisión; sigue siendo la publicación final vigente consultada.

El borrador SP 800-224 de 2024 busca trasladar y actualizar HMAC desde FIPS 198-1, incluyendo SHA-3 y truncamiento. En octubre de 2026 continúa listado como draft en el proyecto NIST. FIPS 198-1 sigue final con propuesta de retirada. Una implementación regulada debe verificar el estado aplicable a su perfil, no citar el borrador como final.

El mensaje debe incluir el contexto que se decide

Un MAC autentica exactamente sus input bytes. Si amount está cubierto pero currency no, un atacante puede cambiar USD por otra unidad sin invalidar el tag. Si el receptor decide por tenant, method, path, content-type, version o key-id, esos campos deben estar cubiertos o fijados por un contexto confiable.

La estructura debe ser inequívoca. MAC(K, user || amount) falla si pares distintos producen los mismos bytes. Se usa una serialización canónica especificada, longitudes, tipos o una codificación binaria estricta. Se rechazan campos duplicados, encodings alternativos y normalizaciones discrepantes antes de decidir qué se verificó.

El contexto también evita cross-protocol attacks. Un tag válido para «mostrar recibo» no debe autorizar «aprobar transferencia». Una entrada conceptual robusta contiene:

domain || version || sender || receiver || operation || sequence || encoded_message

No todos los protocolos necesitan esos campos, pero cada omisión debe justificarse. El receptor y la dirección son importantes cuando la misma clave sirve dos sentidos: sin un role label, una respuesta podría reflejarse como solicitud.

Incluir key-id ayuda a seleccionar una clave, pero ese identificador no puede ordenar aceptar cualquier key. La política local limita algoritmos, claves activas, propósito y periodo. El adversario controla headers transportados.

Separar claves por propósito y dirección

Reutilizar K para MAC, cifrado, derivación y tokens mezcla interfaces. Aunque cada primitiva sea segura en aislamiento, cross-protocol inputs, exposición y rotación se vuelven inseparables. Se derivan subkeys con labels o se generan claves independientes.

También se separan direcciones: K_client_to_server y K_server_to_client. Así un receptor que puede verificar mensajes entrantes no adquiere por la misma clave capacidad de fabricar mensajes que aparentan venir del otro lado.

La separación por tenant, ambiente y versión limita blast radius. Compartir una clave global entre todos los clientes convierte cualquier compromiso en capacidad de falsificación universal y dificulta revocar uno. Una key hierarchy puede reducir almacenamiento, pero la derivación y root key se gobiernan como otro límite de confianza.

Una clave MAC necesita entropía, almacenamiento, acceso mínimo, cryptoperiod, rotación y destrucción. No se obtiene directamente de una contraseña; se usa un password KDF cuando corresponda. Tampoco se imprime como «secret» en variables, logs o tickets.

Truncamiento: seguridad por intento y por volumen

Un tag de t bits permite a un atacante sin información acertar con probabilidad aproximada (2^{-t}) por intento. Tras v intentos independientes, el éxito se aproxima a (v/2^t) mientras la probabilidad sea pequeña. La longitud debe elegirse junto con rate limits, número de verificadores, vida de clave y consecuencias.

RFC 2104 recomienda históricamente no bajar de la mitad de la longitud del hash ni de 80 bits. Perfiles posteriores fijan parámetros concretos; RFC 9053, por ejemplo, registra HMAC 256/64 para un entorno específico y advierte que la fuerza del tag se reduce con el truncamiento. No se extrapola «64 bits siempre bastan».

El protocolo debe fijar t por algoritmo y clave. Un verificador que acepta cualquier prefijo permite al atacante enviar un tag de un byte y acertar 1/256. Comparar sólo la longitud recibida es un fallo clásico. Tags faltantes, largos o cortos se rechazan antes de la comparación semántica, con respuesta externa uniforme cuando proceda.

Los intentos se agregan en todo el sistema. Diez mil dispositivos con la misma clave multiplican el oracle budget. Rotar una clave reinicia material, pero no corrige un tag demasiado corto si el atacante puede repetir contra cada clave.

La telemetría mide fallos por key-id, caller, source y ventana sin registrar tags completos ni claves. Un pico puede ser ataque o desincronización; la respuesta limita intentos, aísla y revisa despliegue.

Verificar no es comparar strings normales

La comparación ingenua termina al encontrar el primer byte distinto. Su tiempo puede filtrar cuántos bytes iniciales eran correctos, permitiendo construir un tag incrementalmente bajo suficientes mediciones. Se usa una primitive de constant-time comparison de la biblioteca.

«Constant time» no significa duración idéntica de toda la request. Antes de comparar pueden existir parseos, key lookup y length checks. El objetivo es que la comparación de valores de igual longitud no filtre la posición del primer mismatch. El diseño reduce además diferencias de respuestas, latencia y logs que creen oráculos.

El orden de verificación importa:

  1. limitar tamaño y forma sin interpretar autoridad;
  2. elegir una clave permitida por configuración, no por algoritmo arbitrario del mensaje;
  3. reconstruir exactamente los bytes autenticados;
  4. recalcular el tag completo y truncarlo según perfil;
  5. comparar con función apropiada;
  6. sólo después parsear o usar campos peligrosos, o validar que el parse previo no tuvo efectos;
  7. comprobar frescura, replay y autorización como gates separados.

Para formatos que requieren parsing antes de reconstruir, el parser debe ser estricto y side-effect free. Nunca se ejecuta una URL callback, se carga una clase o se consulta un recurso controlado antes de autenticar lo necesario.

Replay: un mensaje auténtico puede ser peligroso dos veces

Reenviar el mismo (M,T) no es falsificación. El tag sigue siendo correcto. Si M ordena «transferir 100», el segundo procesamiento puede ser un incidente aunque el MAC funcione perfectamente.

La defensa liga y verifica estado:

El timestamp solo no evita duplicados dentro de la ventana. Un nonce generado por el emisor no sirve si el receptor no detecta reuse. La cache anti-replay debe ser consistente con el alcance de clave, replicación y rollback. Restaurar una snapshot puede olvidar nonces consumidos.

Frescura y autorización se evalúan después de autenticidad de bytes, pero antes de efectos. Registrar «MAC valid» antes de estos gates no equivale a operación aceptada.

Verificación distribuida y blast radius

Entregar la clave a un servicio para verificar también le permite generar tags. Si cien consumidores comparten K, comprometer cualquiera convierte al atacante en emisor válido para todos. La arquitectura debe decidir si esa simetría es aceptable.

Opciones:

Un HSM limita exfiltración, no abuso de una identidad con permiso de MAC generate. Se separan roles de generate y verify si el módulo lo permite, se aplican contextos y cuotas, y se audita cada operación sin registrar input sensible.

Rotación sin ventanas permisivas

Durante rotación pueden coexistir K_old y K_new. El mensaje incluye un key-id autenticado como parte de la entrada o el receptor intenta una lista pequeña gobernada. Nunca acepta cualquier key-id de un almacén amplio ni cae a una clave por defecto.

La secuencia habitual es:

  1. distribuir K_new a verificadores;
  2. comenzar a generar sólo con K_new;
  3. aceptar K_old únicamente durante una ventana definida para mensajes emitidos antes del corte;
  4. medir remanentes y replay;
  5. retirar y destruir K_old.

Si no hay timestamp/sequence autenticado, el receptor no distingue un mensaje old recién creado por una key comprometida de uno legítimo en tránsito. La ventana de rotación amplía el periodo de ataque. Por eso versionado, frescura y key lifecycle se diseñan juntos.

Cambiar de HMAC-SHA-256 a KMAC no es sólo seleccionar otro nombre. Cambian tag construction, parameters, test vectors y quizá libraries. Se despliegan verificadores antes que emisores, se liga algorithm-id a policy, se prueban negativos cross-algorithm y se retira el anterior.

Caso trabajado: webhook firmado pero repetible

Un proveedor envía JSON con headers X-Signature y X-Timestamp. El receptor parsea JSON, lo vuelve a serializar y calcula HMAC-SHA256(secret, body). Algunos eventos fallan porque cambió el orden de campos; el equipo decide verificar sobre la serialización local.

Ese parche crea dos mensajes: bytes recibidos y objeto interpretado. Duplicados, números y Unicode pueden normalizarse distinto. La especificación se cambia para autenticar los raw body bytes y una cadena de contexto inequívoca:

webhook-v1 || timestamp || event-id || method || path || body-length || body

El receptor conserva raw bytes bajo límite, selecciona una key activa por issuer/key-id permitido, recalcula y compara en constant time. Sólo entonces parsea JSON estricto, confirma que event-id interno coincide con el autenticado y autoriza el tipo de evento.

Una auditoría reenvía un request antiguo. El HMAC sigue válido y el sistema duplica un reembolso. Se añade ventana temporal, store de event-id consumido y transición idempotente en la misma transacción que el efecto. La cache se comparte entre replicas y sobrevive restores o fuerza rekey.

Durante rotación, el proveedor envía con K_new; el receptor acepta K_old sólo para timestamps anteriores al corte y dentro de una ventana corta. Métricas distinguen tag inválido, key retirada, timestamp vencido, replay y rechazo de negocio internamente; al caller se devuelve una respuesta que no funciona como oracle detallado.

El resultado no es «webhook seguro porque usa HMAC». Es un contrato: bytes exactos, dominio, identidad de proveedor, clave y versión permitidas, tag length, comparación, frescura, replay state, autorización e idempotencia.

Método de revisión

Para cualquier MAC, responde:

  1. Mecanismo: ¿HMAC, KMAC, CMAC u otro perfil exacto?
  2. Clave: ¿cómo se genera, distribuye, limita, rota y destruye?
  3. Poseedores: ¿quién puede generar, no sólo verificar?
  4. Bytes: ¿qué representación exacta entra al tag?
  5. Contexto: ¿dominio, versión, dirección, emisor, receptor y operación están ligados?
  6. Parámetros: ¿algoritmo y tag length están fijados por policy?
  7. Verificación: ¿longitud exacta, recompute y constant-time compare?
  8. Oráculos: ¿qué revelan errores, timing, logs y rate limits?
  9. Frescura: ¿timestamp, nonce o sequence se comprueba con estado?
  10. Autorización: ¿qué policy decide el efecto después del tag?
  11. Concurrencia: ¿replay store e idempotencia son atómicos y compartidos?
  12. Rotación: ¿cómo coexisten claves sin fallback abierto?
  13. Evidencia: ¿hay vectors positivos, negativos, truncados, replay y cross-domain?

Un tag que coincide es una evidencia intermedia. El sistema todavía debe decidir si el mensaje pertenece a esta ejecución y si su efecto está permitido.

La revisión debe incluir test vectors oficiales y negativos propios. Un vector positivo demuestra interoperabilidad básica; no demuestra que el parser cubra los mismos bytes, que una longitud incorrecta se rechace o que el replay store sea atómico. Se prueban mensajes vacíos y grandes, un bit alterado en cada campo, key-id desconocido, algoritmo no permitido, tags ausentes, cortos, largos y correctos para otro dominio, además de concurrencia sobre el mismo operation-id. Las pruebas de rotación deben demostrar que K_new genera, que K_old deja de generar, que su ventana de verificación termina y que un mensaje posterior al corte no puede presentarse con la clave antigua. La evidencia operacional completa así lo que un vector criptográfico aislado no observa.

Transferencia al capítulo 65

El MAC autentica bytes sin ocultarlos. El capítulo 65 estudiará cifrado simétrico, modos y AEAD: confidencialidad, nonce/IV, datos asociados, encrypt-then-MAC y por qué no debe componerse cifrado y autenticación manualmente.

Síntesis

Un MAC introduce una clave secreta para que un adversario sin ella no pueda producir tags válidos de mensajes nuevos. La autenticidad pertenece al conjunto de poseedores de la clave; no concede no repudio, verdad ni autorización. El mensaje permanece visible y un par válido puede repetirse.

HMAC es una construcción interna/externa definida, no H(secret||message). KMAC es una construcción KECCAK con customization y salida variable, no HMAC-SHA3. Algoritmo, clave, tag length, domain y encoding forman parte del contrato.

La verificación exige longitud fija, reconstrucción exacta, comparación resistente a timing, policy local de claves y gates separados de frescura, replay y autorización. El truncamiento se evalúa por intentos agregados y vida de clave. Compartir claves amplía capacidad de falsificación y blast radius.

La seguridad profesional no termina en MAC valid; comienza al interpretar exactamente qué autentica ese resultado y qué todavía falta decidir.

Fuentes primarias y documentación técnica