Firma y verificación de solicitudes
La firma de la solicitud prueba la autenticidad del remitente y la integridad del contenido. A diferencia del TLS (que protege el canal), la firma de la aplicación hace que cada mensaje sea verificable y resistente a proxy, caché y entrega diferida.
Objetivos:1. Autenticidad (quien envió) e integridad (no ha cambiado).
2. No repetibilidad (protección contra réplicas).
3. Desvío de transporte (funciona sobre HTTP, colas, webhooks).
4. Auditabilidad (reproducible meses después).
1) Modelo de amenazas (mínimo)
Reemplaza el cuerpo/los encabezados en la ruta a seguir.
Replay (repetición de una petición legítima).
Downgrade/strip encabezados de firma.
Robando secretos de integración.
Reloj no sincrónico (clock skew) y colas largas.
2) Selección de la primitiva
HMAC (simetría): simple y rápido, la clave se almacena en ambos lados. Adecuado para webhooks B2B y API internas.
RSA/ECDSA (asimetría): clave privada del remitente, pública en el destinatario. Adecuado para integraciones abiertas y cuando es importante no compartir un secreto.
mTLS: autenticación mutua a nivel de transporte; a menudo se combina con la firma NMAS/cuerpo.
JWT/JWS: conveniente para los tokens bearer y los estigmas autosuficientes; para la firma corporal, es mejor utilizar canonicalización + JWS Detached/HTTP Message Signatures.
Señales de mensajes HTTP (firma de las partes seleccionadas de la consulta): un acercamiento moderno para NAT.
Recomendación: para webhooks - HMAC + timestamp + nonce + canonicalización corporal; para la API pública - Señales de mensajes HTTP o JWS; con altos riesgos: agregue mTLS.
3) Canonicalización (que firmamos exactamente)
Es necesario firmar una cadena determinista, igualmente recuperable por ambas partes.
Composición de referencia:
method \n path_with_query_normalized \n content-type \n digest: SHA-256=BASE64(SHA256(body)) \n x-ts: <unix iso> \n x-nonce: <uuid> \n host \n x-tenant: <tenant_id> \n
Línea final:
canonical = join("\n", fields)
signature = HMAC(secret, canonical) # или ECDSA_sign(private_key, canonical)
Reglas:
- Normalice la ruta y el orden de los parámetros query.
- Espacios/unicode/mayúsculas y minúsculas - Fijar (por ejemplo, lower-case de títulos, trim).
- Cuerpos grandes - hash (Digest) en lugar de incluir «tal cual».
4) Formato de encabezado
Ejemplo para HMAC:
X-Signature-Alg: hmac-sha256
X-Signature: v1=hex(hmac),ts=1730379005,nonce=550e8400-e29b-41d4-a716-446655440000,kid=prov_42
Digest: SHA-256=BASE64(SHA256(body))
X-Tenant: brand_eu
Ejemplo para asimetría (ECDSA P-256):
Signature: keyId="prov_42", alg="ecdsa-p256-sha256",
ts="2025-10-31T12:30:05Z", nonce="550e...", headers="(request-target) host digest x-tenant",
sig="BASE64(raw_signature)"
Donde 'kid '/' keyId' permite seleccionar una clave del registro (ver rotación).
5) Verificación en el lado de recepción
Pseudocódigo:python def verify(request):
1) Basic assert abs (now () - request. ts) <= ALLOWED_SKEW # напр., 300 с assert not replayed(request. nonce, window = TTL) # store nonce/ts in KV
2) Restore canonical canonical = build_canonical (
method=request. method,
path=normalize_path(request. path, request. query),
content_type=request. headers["content-type"],
digest=hash_body(request. body),
ts=request. ts,
nonce=request. nonce,
host=request. headers["host"],
tenant=request. headers. get("x-tenant")
)
3) Get the key key = key_registry. get(request. kid) # secret (HMAC) или public key (ECDSA)
4) Verify if request signature. alg. startswith("hmac"):
ok = hmac_compare(key. secret, canonical, request. signature)
else:
ok = asym_verify(key. public, canonical, request. signature)
5) Solution if not ok: return 401, "SIGNATURE_INVALID"
return 200, "OK"
Comparación de tiempo constante HMAC, almacenamiento 'nonce '/' (ts, event_id)' en KV rápidos (TTL ≥ ventana de entrega).
6) Anti-réplica y ventanas
Timestamp + Nonce: rechazar solicitudes de más de '± Δ' (por ejemplo, 5 min) y repeticiones de nonce en esta ventana.
Para webhooks: use un 'event _ id' estable y una tabla de inbox - es más confiable que sólo nonce.
La retransmisión (retraídas) debe utilizar los mismos ts/nonce/event_id en lugar de generar nuevos.
7) Multi-tenant y regiones
Almacene las claves per tenant/region: 'kid = <tenant>: <region>: <key _ id>'.
Comparta las agrupaciones de secretos y los límites; cumpla con la residencia de datos.
En las cabeceras/canonicalizaciones, especifique 'X-Tenant' y la región es parte del contexto verificable.
8) Gestión de claves y rotación
Registro de claves (KMS/Vault): 'kid', tipo, algoritmo, estado ('active', 'deprecating', 'retired'), 'valid _ from/valid _ to'.
Dual-secret: mantenga la tecla actual y la siguiente al mismo tiempo (el receptor acepta ambas).
Rotación por calendario y por evento (compromiso).
Pinning clave (si es posible) y restringir el acceso a los materiales de clave.
Logs de acceso a las claves y acciones con ellas.
9) Combinación con mTLS y OAuth
mTLS comprueba el canal y «quién eres» a nivel de certificado.
La firma protege el mensaje (útil a través de proxy/caches/colas).
OAuth/JWT complementa la autenticación/autorización, pero de por sí no garantiza la integridad del cuerpo (si no se firma en canonicalización).
Mejores prácticas: mTLS + firma corporal (Digest) + HMAC/ECDSA + breve 'ts' -intervalo.
10) Errores y códigos de respuesta
'401 SIGNATURE_INVALID' es una firma/algoritmo incorrecto.
'401 KEY_REVOKED' -' kid 'no es válido/caducado.
'400 TIMESTAMP_OUT_OF_RANGE' - reloj/ventana.
'409 NONCE_REPLAYED' - Se detectó una repetición.
'400 DIGEST_MISMATCH' - cuerpo cambiado.
'415 UNSUPPORTED_ALGORITHM' - sin resolver' alg '.
'429 TOO_MANY_ATTEMPTS' - Trottling por clave/tenant.
Pinche la razón exacta en el 'error _ code' legible a máquina; no devuelva los secretos/canonicalización «tal cual».
11) Observabilidad y auditoría
Métricas:- `verify_p95_ms`, `verify_error_rate`, `digest_mismatch_rate`, `replay_blocked_rate`, `alg_usage{hmac,ecdsa}`, `clock_skew_ms`.
- Los registros (estructurales) son: 'kid', 'alg', 'tenant', 'region', 'ts',' nonce ',' digest _ hash ',' decision ',' reason '.
- Treking: atributos 'signature. kid`, `signature. alg`, `signature. ts_skew`.
- Auditoría: registro de rotación sin cambios, uso de claves y marcas de tolerancia.
12) Rendimiento
Hashing el cuerpo por streaming (no guardar en la memoria).
Guarda en caché las claves públicas por 'kid' con TTL corto y discapacidad por evento.
En edge/gateway, realice comprobaciones previas (ts/nonce/formato).
HMAC es más rápido que ECDSA; ECDSA es más conveniente para integraciones externas y claves «no compartibles».
13) Pruebas
Conjuntos de fixtures: las mismas solicitudes → la misma canonicalización/firma; las brechas «sucias »/orden de query/encabezados son → sostenibles.
Negative: incorrecto 'kid/alg', cuerpo/host modificado, nonce repetición, ts obsoleto, clock skew.
Property-based: cualquier consulta equivalente da una sola cadena canónica.
Interop: revisiones de idioma cruzado (Go/Java/Node/Python).
Chaos: retrasos, retraídas, cambio de llave «sobre la marcha».
14) Playbooks (runbooks)
1. Estallido de 'SIGNATURE _ INVALID'
Comprobar la rotación de las llaves, el clock de rassinchron, los cambios de canonicalización en el remitente.
Habilitar temporalmente 'dual-accept' para el antiguo' kid ', notificar al socio.
2. Crecimiento de 'REPLAYED'
Aumentar el almacenamiento TTL nonce, taladrar los retransmisores en el remitente, comprobar el clock skew.
Superponer el IP/ASN abusivo en el edge.
3. 'DIGEST _ MISMATCH' masivamente
Comprobar proxy/compresión/sobrescribir títulos; fijar la versión de canonicalización.
Desactivar los intermediarios que violan el cuerpo/encabezados.
4. Compromiso de clave
Inmediatamente revoke 'kid', traducir a 'next _ kid', regenerar todos los secretos/tokens, auditoría de acceso.
15) Errores típicos
Firmar «parte del cuerpo» o JSON sin fijar el orden → una vulnerabilidad a la permutación de campos.
La falta de 'Digest' → proxy puede cambiar el cuerpo discretamente.
La ventana larga 'ts' sin nonce → está abierta por una réplica.
Almacenar secretos en variables de entorno sin KMS/Vault.
Comparar firma no es tiempo constante.
Ignorar 'host '/' path' en canonicalización → ataques de reenvío.
Mezclar 'kid' diferentes tenantes y regiones.
16) Lista de verificación antes de la venta
- Definido el formato de canonicalización (method, path + query, content-type, Digest, ts, nonce, host, tenant).
- Implementado por HMAC/ECDSA con 'kid', registro de claves y dual-secret.
- Se incluyen anti-réplica (nonce + ts) y almacenamiento de inbox/event_id para webhooks.
- Códigos de error/política de retroceso y trottling por tenant/key personalizados.
- Observabilidad: métricas de verify, registros, rastreo, alertas para ráfagas.
- La rotación de claves está automatizada; auditorías y derechos de acceso restringidos.
- Kits de prueba de canonicalización y compatibilidad entre idiomas.
- Documentación para integradores con ejemplo en 3-4 idiomas y fixtures.
- mTLS está habilitado para integraciones sensibles; JWT sólo se utiliza como complemento, no como sustituto de la firma del cuerpo.
Conclusión
La firma y verificación de solicitudes no es «un solo titular», sino una disciplina: canonicalización clara, ventanas cortas de tiempo, anti-réplica, rotación de claves y observabilidad. Construye un estándar único para todas las integraciones (API y webhooks), usa 'kid '/KMS, acepta dos claves al rotar y tus bucles se volverán resistentes a los sustituciones, predecibles y fáciles de auditar.