Docs / 06. Protocolo Criptográfico HMAC y Anti-Replay

Protocolo criptográfico inter-servicio: HMAC-SHA256 canónico, anti-replay con X-Nonce y consumo desacoplado en jg_filmmaker

Autenticación simétrica máquina a máquina, mitigación de repetición y comunicación agnóstica entre planos.

🎙️
Escucha el artículo elaborado con IA AUDIO IA
Escucha el análisis arquitectónico completo elaborado en alta fidelidad
Protocolo criptográfico inter-servicio: HMAC-SHA256 canónico, anti-replay con X-Nonce y consumo desacoplado en jg_filmmaker

"En el diseño de protocolos de enlace y transporte máquina a máquina (M2M), la seguridad jamás debe apoyarse en la confianza implícita ni en sesiones de usuario. La interconexión entre servicios desacoplados requiere autenticación criptográfica sin estado: strings canónicos normalizados, ventanas temporales estrictas y nonces de un solo uso en almacenamiento atómico. La criptografía simétrica bien aplicada es la base para desacoplar el plano de ejecución del agente y permitir su consumo seguro desde cualquier cliente heterogéneo."


1. Resumen Ejecutivo: La Necesidad del Microkernel Agnóstico

En la arquitectura de sistemas distribuidos con agentes de IA, un error recurrente consiste en acoplar estrechamente el motor de ejecución del agente a una única interfaz o portal web. Cuando el runtime asume que su único consumidor es su panel de administración, la arquitectura pierde modularidad, escalabilidad y la capacidad de ser consumida por canales heterogéneos (APIs externas, bots de mensajería, pipelines batch o aplicaciones satélite).

Para lograr un desacoplamiento verdadero, el motor del agente (jg_agente) debe operar como un microkernel agnóstico de ejecución que expone una API REST protegida por un protocolo criptográfico robusto e interoperable. Este protocolo no depende de sesiones de usuario ni de cookies; autentica a nivel de máquina a máquina (M2M) garantizando integridad del payload, autenticidad del origen y mitigación estricta contra ataques de repetición (Replay Attacks).

En este artículo analizamos el protocolo criptográfico implementado en el ecosistema javiergalvez-ia: construcción de firmas canónicas HMAC-SHA256 multi-campo (METHOD + PATH + TIMESTAMP + NONCE + SHA256(BODY)), mitigación anti-replay en ventana temporal estrecha con deduplicación atómica de X-Nonce en caché distribuida, y el caso de estudio real de jg_filmmaker (aplicación Laravel satélite especializada en guionización y análisis audiovisual) consumiendo directamente jg_agente con fallback heurístico local e independencia total del dashboard central.


2. Puntos de Fricción: APIs Expuestas, Secretos Inseguros y Ataques de Repetición

Al inspeccionar comunicaciones inter-servicio en plataformas de IA, surgen con frecuencia los siguientes compromisos de seguridad:

  1. Tokens Bearer Estáticos y Secretos Compartidos en Texto Plano: Servicios que utilizan un simple Authorization: Bearer my-secret-token. Si un atacante intercepta una petición o accede a logs intermedios de red, puede reutilizar ese token indefinidamente para ejecutar comandos en el agente.

  2. Vulnerabilidad a Ataques de Repetición (Replay Attacks): Incluso si la petición viaja cifrada por TLS/HTTPS, un vector con capacidad de interceptación en un gateway corporativo, proxy inverso o balanceador comprometido puede capturar la trama completa y reenviarla. Si la llamada es POST /api/v1/executions (por ejemplo, ordenar al agente "Aprobar despliegue #104"), el agente ejecutará la orden tantas veces como se repita el paquete.

  3. Acoplamiento Monolítico entre Planos: Sistemas donde cualquier aplicación cliente debe pasar obligatoriamente por la interfaz gráfica o la base de datos del dashboard central para interactuar con el agente, creando cuellos de botella en la red y puntos únicos de fallo (SPOF).


3. Arquitectura y Solución Técnica: Protocolo HMAC Canónico con X-Nonce

El protocolo implementado formaliza una frontera de seguridad simétrica y sin estado entre cualquier cliente autorizado y el Execution Plane.

Diagrama de Comunicación Criptográfica M2M

┌──────────────────────────────────────────────┐
│             Emisor (jg_dashboard)            │
│                                              │
│  1. Genera X-Timestamp (Unix epoch)          │
│  2. Genera X-Nonce (UUIDv4)                  │
│  3. Calcula Body SHA256 Hash                 │
│  4. Canonical String:                        │
│     METHOD\nPATH\nTIMESTAMP\nNONCE\nHASH     │
│  5. HMAC-SHA256(Secret, CanonicalString)     │
└──────────────────────┬───────────────────────┘
                       │
                       │ HTTP POST con Cabeceras Criptográficas:
                       │ - X-Timestamp: 1773321600
                       │ - X-Nonce: 4f3a7c8e-b12a-49d1-b0e5-789a6c1e3456
                       │ - X-Signature: a9f8e4...
                       ▼
┌──────────────────────────────────────────────┐
│             Receptor (jg_agente)             │
│                                              │
│  [Paso 1] Validación de Ventana Temporal     │
│           ¿|now - X-Timestamp| <= 300 seg?   │
│           └─► NO: 401 Unauthorized           │
│                                              │
│  [Paso 2] Detección de Replay vía X-Nonce    │
│           Cache::add("nonce:{X-Nonce}", 600) │
│           ¿Clave ya existía en Caché?        │
│           └─► SÍ: 401 Replay Attack Detected │
│                                              │
│  [Paso 3] Reconstrucción de String Canónico  │
│           y Hash SHA256 del Body             │
│                                              │
│  [Paso 4] Comparación en Tiempo Constante    │
│           hash_equals(expected, received)    │
│           └─► OK: Petición Procesada         │
└──────────────────────────────────────────────┘

Reglas Inmutables del Protocolo:

  1. Canonicidad Estricta: Cada campo que compone la firma se concatena con separadores explícitos: METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_HASH. Esto impide ataques de manipulación de rutas o mutación de cabeceras.

  2. Ventana de Tolerancia Temporal Estricta: Las peticiones con una discrepancia temporal mayor a 300 segundos (5 minutos) respecto al reloj del servidor receptor son descartadas para mitigar desfases de reloj y acotar el almacenamiento de nonces.

  3. Registro Atómico de Nonce (Anti-Replay): Cada X-Nonce (UUIDv4) se registra de forma atómica en una caché en memoria (Redis en producción, Cache::add en Laravel o NonceReplayCache en Python) con un TTL equivalente al doble de la ventana temporal (600 segundos). Si el mismo nonce se recibe por segunda vez dentro de la ventana, la petición se rechaza con código 401 Unauthorized.

  4. Comparación en Tiempo Constante: La verificación de la firma utiliza hash_equals() en PHP o hmac.compare_digest() en Python para prevenir ataques de canal lateral basados en temporización (Timing Attacks).


4. Código Fuente Comentado

Generación de Firma y Nonce en jg_filmmaker (Cliente Laravel)

// jg_filmmaker/app/Services/JgAgente/AgentClientService.php

namespace App\Services\JgAgente;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;

class AgentClientService
{
    protected function generateHmacHeaders(string $method, string $path, string $body = ''): array
    {
        $secret = config('services.jg_agente.secret');
        if (empty($secret)) {
            throw new RuntimeException("Secreto HMAC no configurado para AgentClientService");
        }

        $timestamp = (string) time();
        $nonce = (string) Str::uuid(); // Generación de Nonce anti-replay único
        $bodyHash = hash('sha256', $body);

        // String canónico normalizado
        $canonical = implode("\n", [
            strtoupper($method),
            $path,
            $timestamp,
            $nonce,
            $bodyHash,
        ]);

        $signature = hash_hmac('sha256', $canonical, $secret);

        return [
            'X-Timestamp' => $timestamp,
            'X-Nonce'     => $nonce,
            'X-Signature' => $signature,
            'Content-Type'=> 'application/json',
        ];
    }
}

Middleware de Validación y Caché Atómica en jg_dashboard

// jg_dashboard/app/Http/Middleware/VerifyHmacSignature.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class VerifyHmacSignature
{
    public function handle(Request $request, Closure $next): Response
    {
        $timestamp = $request->header('X-Timestamp');
        $nonce = $request->header('X-Nonce');
        $signature = $request->header('X-Signature');

        // 1. Verificación de presencia y frescura temporal
        if (! $timestamp || ! $nonce || ! $signature) {
            return response()->json(['error' => 'Cabeceras de firma incompletas'], 401);
        }

        if (abs(time() - (int) $timestamp) > 300) {
            return response()->json(['error' => 'Petición expirada (desfase temporal)'], 401);
        }

        // 2. Control Anti-Replay: Inserción atómica en caché
        $cacheKey = "hmac_nonce:{$nonce}";
        if (! Cache::add($cacheKey, true, 600)) {
            return response()->json(['error' => 'Ataque de repetición detectado: Nonce reutilizado'], 401);
        }

        // 3. Reconstrucción canónica y comparación en tiempo constante
        $canonical = implode("\n", [
            strtoupper($request->method()),
            $request->getRequestUri(),
            $timestamp,
            $nonce,
            hash('sha256', $request->getContent()),
        ]);

        $expectedSignature = hash_hmac('sha256', $canonical, config('security.hmac_secret'));

        if (! hash_equals($expectedSignature, $signature)) {
            return response()->json(['error' => 'Firma HMAC inválida'], 401);
        }

        return $next($request);
    }
}

Verificación Simétrica en jg_agente (FastAPI / Python)

# jg_agente/app/security/auth.py

import hmac
import hashlib
import time
from fastapi import Request, HTTPException, Security
from app.core.config import settings

class NonceReplayCache:
    """Caché en memoria con expiración para mitigación de repetición."""
    _seen: dict[str, float] = {}

    @classmethod
    def check_and_set(cls, nonce: str, ttl: float = 600.0) -> bool:
        now = time.time()
        cls._cleanup(now)
        if nonce in cls._seen:
            return False  # Nonce ya utilizado
        cls._seen[nonce] = now + ttl
        return True

    @classmethod
    def _cleanup(cls, now: float) -> None:
        cls._seen = {k: v for k, v in cls._seen.items() if v > now}

async def verify_hmac_request(request: Request):
    timestamp = request.headers.get("X-Timestamp")
    nonce = request.headers.get("X-Nonce")
    signature = request.headers.get("X-Signature")

    if not timestamp or not nonce or not signature:
        raise HTTPException(status_code=401, detail="Cabeceras criptográficas incompletas")

    if abs(time.time() - float(timestamp)) > 300:
        raise HTTPException(status_code=401, detail="Petición expirada por desfase temporal")

    if not NonceReplayCache.check_and_set(nonce):
        raise HTTPException(status_code=401, detail="Nonce reutilizado: Replay Attack detectado")

    body = await request.body()
    canonical = f"{request.method}\n{request.url.path}\n{timestamp}\n{nonce}\n{hashlib.sha256(body).hexdigest()}"
    expected = hmac.new(settings.HMAC_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, signature):
        raise HTTPException(status_code=401, detail="Firma criptográfica inválida")

5. Consideraciones de Producción, Trade-offs y Límites

  1. Topologías Heterogéneas y Consumo Autónomo: Gracias a este protocolo, otro dashboard puede desplegarse en un servidor totalmente independiente y comunicarse directamente con jg_agente sin requerir que jg_dashboard esté activo.

  2. Supuestos Operativos: Sincronización Horaria Estricta con NTP: El mecanismo de frescura temporal (ventana de 300 segundos) asume una condición operativa indispensable: todos los nodos del clúster deben mantener sus relojes sincronizados mediante NTP (chrony o systemd-timesyncd).

    • Si un nodo emisor o receptor sufre un desfase de reloj (clock drift) acumulado superior a 5 minutos respecto a UTC, el sistema comenzará a rechazar sistemáticamente tráfico legítimo con errores 401 Unauthorized.
    • En infraestructuras multi-región o entornos de virtualización donde los relojes virtuales pueden experimentar saltos temporales tras una congelación de hipervisor, la monitorización continua del offset de NTP en el pipeline de observabilidad es obligatoria.
  3. Gestión de Secretos Compartidos y Estrategia de Rotación de Claves: La autenticación basada en HMAC simétrico comparte una clave secreta entre emisor y receptor. La rotación de esta clave en producción presenta un reto operativo: si ambos servicios no actualizan la clave en el mismo instante exacto, las peticiones en tránsito serán rechazadas.

    • Solución implementada: Para permitir rotaciones seguras sin tiempo de inactividad (zero-downtime key rotation), el validador del receptor debe admitir una configuración de clave dual: una clave primaria activa y una clave previa en período de gracia. Durante una ventana de transición programada, el receptor valida contra ambas claves; una vez que todos los clientes han actualizado sus variables de entorno, la clave antigua se desactiva definitivamente.
  4. Tratamiento del Body en Métodos HTTP sin Payload: Para peticiones sin cuerpo (GET, DELETE), el hash SHA-256 se calcula sobre una cadena vacía (hash('sha256', '') = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855), manteniendo la estructura canónica idéntica sin introducir bifurcaciones condicionales en el protocolo.


6. Reflexión de Arquitectura: Desacoplar para Sobrevivir

En la ingeniería de software distribuido existe un principio claro: si dos componentes no pueden comunicarse de forma autónoma a través de un protocolo formal, no constituyen dos sistemas desacoplados, sino un monolito distribuido por la red.

En este ecosistema, la validación del modelo de arquitectura fue el desarrollo de jg_filmmaker: una aplicación Laravel completa para la industria audiovisual con sus propias vistas, modelos y ciclo de vida. Conectar jg_filmmaker a jg_agente mediante un cliente HTTP hermético protegido por HMAC, sin dependencias del dashboard administrativo y con un fallback heurístico local si la red se degrada, confirma la validez del desacoplamiento. El agente de IA debe operar como un servicio composable y seguro, preservando la independencia de los sistemas satélite.


7. Enlaces y Referencias en el Repositorio

Metadatos del documento
Título Protocolo criptográfico inter-servicio: HMAC-SHA256 canónico, anti-replay con X-Nonce y consumo desacoplado en jg_filmmaker
Archivo docs/02_jg_agent/articulos/06_hmac_cross_plane_heterogeneous_topologies.md
Descripción Autenticación simétrica máquina a máquina, mitigación de repetición y comunicación agnóstica entre planos.
Autor Javier Gálvez
Fecha 2026-10-17
Etiquetas
#Artículos #Arquitectura #HMAC #Seguridad #M2M #Anti-Replay #X-Nonce #jg_filmmaker