AEAD en producción: por qué es el estándar
En producción, el cifrado simétrico casi siempre debe hacerse con AEAD (Authenticated Encryption with Associated Data): un esquema que cifra y además autentica el mensaje. Esto evita que un atacante modifique el ciphertext sin ser detectado y reduce errores de integración.
Dos opciones recomendadas y ampliamente soportadas son:
- AES-GCM: muy rápido con aceleración hardware (AES-NI). Requiere especial cuidado con la unicidad del nonce.
- ChaCha20-Poly1305: excelente rendimiento en móviles/entornos sin AES-NI; también requiere nonce único.
Por qué AEAD se prefiere frente a “cifrar + hashear”
Los esquemas caseros tipo “cifrar y luego hashear” o “hashear y luego cifrar” suelen fallar por detalles: orden incorrecto, MAC sobre datos equivocados, comparación no constante, reutilización de claves, o no autenticar metadatos. AEAD ya define una construcción segura y probada: produce un tag de autenticación que cubre el ciphertext y opcionalmente datos asociados (AAD). En la práctica: si el tag no valida, no se devuelve nada.
Nonce/IV: significado, requisitos y consecuencias
Qué es un nonce/IV en AEAD
Un nonce (number used once) o IV es un valor público que se combina con la clave para asegurar que dos cifrados del mismo plaintext produzcan resultados distintos. En AEAD, el nonce es parte esencial del cálculo del tag.
Regla crítica: unicidad por clave
Para AES-GCM y ChaCha20-Poly1305, el requisito práctico es: no repetir jamás el mismo nonce con la misma clave. No es “ideal”, es una condición de seguridad.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- AES-GCM: la reutilización de nonce con la misma clave puede revelar relaciones entre plaintexts y, en escenarios reales, permitir forjar tags (integridad rota).
- ChaCha20-Poly1305: reutilizar nonce con la misma clave reutiliza el keystream (confidencialidad rota) y debilita autenticación.
Tamaños típicos y formato
- AES-GCM: nonce de 96 bits (12 bytes) es el formato recomendado; otros tamaños implican procesamiento adicional y más riesgo de errores.
- ChaCha20-Poly1305: nonce de 96 bits (12 bytes) en la variante IETF (la más común).
Cómo generar nonces de forma segura
Hay dos estrategias seguras; elige una y aplícala consistentemente:
Estrategia A: nonce aleatorio (simple, pero con límites)
Genera 12 bytes aleatorios con un CSPRNG por cada cifrado. Esto es fácil, pero debes considerar el riesgo de colisión: con suficientes mensajes, la probabilidad de repetir un nonce crece (paradoja del cumpleaños). Para volúmenes altos por clave (p. ej., millones/billones de mensajes), es preferible un esquema determinista.
Estrategia B: nonce determinista con contador (recomendado a gran escala)
Mantén un contador monotónico por clave (persistente y atómico). Construye el nonce como, por ejemplo, nonce = prefix_aleatorio(4 bytes) || contador(8 bytes big-endian). El prefijo distingue instancias (reinicios/hosts) y el contador garantiza unicidad. Requisitos:
- El contador debe persistir (disco/DB) y actualizarse de forma atómica.
- En despliegues distribuidos, cada emisor debe tener un prefijo único o un rango de contadores asignado.
Qué pasa si se reutiliza un nonce
En AEAD moderno, la reutilización de nonce suele ser un fallo catastrófico. En incidentes reales, esto ha permitido recuperar plaintexts o falsificar mensajes. Por eso, en diseño de sistemas, el manejo de nonces debe tratarse como un requisito de ingeniería (estado, concurrencia, reinicios), no como un detalle criptográfico.
Estructura de un “paquete cifrado” (envelope) recomendado
En producción conviene definir un formato explícito para almacenar/transmitir datos cifrados. Un paquete típico incluye:
- version: versión del formato (y/o del algoritmo).
- key_id (kid): identificador de la clave usada (para rotación).
- nonce: 12 bytes (público).
- ciphertext: datos cifrados.
- tag: autenticación (p. ej., 16 bytes).
- AAD: no se almacena necesariamente dentro del paquete; puede ser metadato externo, pero debe ser reproducible exactamente para descifrar.
Una representación binaria simple puede ser:
struct EnvelopeV1 { u8 version = 1; u8 alg_id; // 1=AES-GCM, 2=ChaCha20-Poly1305 u16 kid_len; bytes[kid_len] kid; bytes[12] nonce; u32 ct_len; bytes[ct_len] ciphertext; bytes[16] tag;}O una representación textual (p. ej., JSON) donde nonce, ciphertext y tag van en Base64. Si usas JSON, fija un orden canónico o evita firmar/validar el JSON “tal cual”; en AEAD, lo importante es que AAD y campos sean idénticos al descifrar.
Qué poner en AAD (Associated Authenticated Data)
AAD son datos no cifrados pero autenticados. Útil para evitar ataques de “cambio de contexto” (copiar un ciphertext válido a otro lugar donde tenga otro significado). Ejemplos de AAD:
- Identificador de usuario/tenant.
- Nombre del campo (p. ej.,
"email","ssn"). - Ruta lógica del recurso (p. ej.,
"/invoices/2026/02"). - Versión del esquema de datos.
Regla: si un dato afecta al significado o autorización del plaintext, debe ir en AAD o dentro del plaintext cifrado.
Guía práctica paso a paso (patrón de implementación)
1) Deriva/selecciona la clave correcta
Usa una clave simétrica de tamaño adecuado (p. ej., 256 bits) y gestiona su ciclo de vida (KMS/HSM/secret manager). Para separar usos, es buena práctica tener claves distintas por propósito (archivos vs campos vs tokens), o derivar subclaves con HKDF a partir de una clave maestra (según tu arquitectura).
2) Define el AAD del contexto
Construye AAD de forma determinista. Ejemplo: aad = "tenant=acme|table=users|field=email|v=1". Evita concatenaciones ambiguas; usa delimitadores claros o un formato binario con longitudes.
3) Genera un nonce único
Elige estrategia aleatoria o contador. Si tu sistema cifra a gran escala o en múltiples nodos, prefiere contador con prefijo por instancia.
4) Cifra con AEAD y produce (ciphertext, tag)
La API típica devuelve ciphertext y tag juntos o concatenados. Guarda nonce y metadatos (version, alg, kid).
5) Al descifrar: valida primero, luego entrega plaintext
El descifrado debe fallar si el tag no valida. No devuelvas plaintext parcial. No intentes “arreglar” errores.
Errores y canales laterales: cómo validar sin filtrar información
Reglas de manejo de errores
- Devuelve un error genérico:
"invalid ciphertext"para cualquier fallo (nonce malformado, tag inválido, versión desconocida, etc.). - No distingas en logs accesibles al atacante entre “tag inválido” y “kid no existe”. Si necesitas observabilidad, registra detalles solo en canales internos protegidos.
- Evita ramas que cambien el tiempo de respuesta según el punto de fallo. En la práctica, usa bibliotecas que ya implementen verificación en tiempo constante y minimiza diferencias de comportamiento.
Orden recomendado al descifrar
Para reducir filtraciones:
- Parsea y valida longitudes/formato de forma estricta.
- Resuelve
kida clave (si no existe, usa una clave “dummy” y sigue hasta fallar en autenticación, o unifica tiempos con mitigaciones operativas; depende del riesgo y del entorno). - Ejecuta AEAD open/verify.
- Si falla, retorna error genérico.
Nota: la mitigación de tiempos perfectos es difícil; el objetivo práctico es evitar oráculos obvios y mensajes de error diferenciados.
Versionado, rotación de claves y compatibilidad
Versionado del formato
Incluye un version del envelope para poder evolucionar:
- v1: AES-GCM con nonce 12B, tag 16B, AAD canónica.
- v2: ChaCha20-Poly1305 o cambio de serialización.
El versionado debe ser explícito y validado antes de descifrar.
Rotación de claves (kid + re-cifrado)
Patrón recomendado:
- Cada ciphertext almacena
kid. - El servicio mantiene un conjunto de claves activas: una clave de escritura (actual) y varias claves de lectura (anteriores).
- Al leer: intenta con la clave indicada por
kid. - Al escribir: usa siempre la clave de escritura.
- Re-cifra de forma gradual (lazy re-encryption): cuando descifras con una clave antigua, vuelves a cifrar con la nueva y actualizas.
Rotación de algoritmo
Si cambias de AES-GCM a ChaCha20-Poly1305, trátalo como cambio de versión/algoritmo en el envelope. No intentes “compatibilidad implícita”.
Ejemplos prácticos (campos, archivos y secretos) con límites
Cifrado de campos en base de datos (PII, tokens, datos sensibles)
Objetivo: cifrar valores individuales (p. ej., email, document_id) para reducir exposición ante fugas de DB.
Recomendación de diseño
- Usa AEAD con AAD que incluya
tenant,tabla,campoy quizá elrecord_id. - Genera un nonce único por valor cifrado.
- Guarda el envelope completo en una columna binaria o texto Base64.
Paso a paso
- Define
aad = "tenant=T1|table=users|field=email|id=123". - Genera
nonce(12B). - Cifra
plaintext=emailcon AEAD usandoaad. - Almacena
version, alg, kid, nonce, ciphertext, tag.
Límites y advertencias
- Búsquedas: cifrado aleatorio no permite buscar por igualdad sin técnicas adicionales. Si necesitas búsqueda, evalúa índices derivados (p. ej., HMAC del valor normalizado) con claves separadas y controles de acceso estrictos.
- Normalización: normaliza antes de cifrar (p. ej., emails en minúsculas) para evitar duplicados lógicos.
- Longitudes: el ciphertext crece (nonce+tag+overhead). Considera tamaños de columna.
Cifrado de archivos (objetos en S3, blobs, backups)
Para archivos grandes, el patrón típico es cifrado por chunks con AEAD, o usar un esquema de “envelope encryption”: una clave de datos (DEK) por archivo, cifrada con una clave maestra (KEK) en KMS.
Patrón recomendado: DEK por archivo + AEAD por chunk
- Genera una DEK aleatoria (p. ej., 256 bits) por archivo.
- Para cada chunk: nonce único derivado de un contador de chunk (y un prefijo aleatorio por archivo).
- AAD incluye: id de archivo, número de chunk, versión.
- Guarda metadatos del archivo:
kidde KEK, DEK cifrada (envelope), tamaño de chunk, etc.
file_header: {version, alg, kek_kid, encrypted_DEK, file_nonce_prefix}chunk_i: {nonce = prefix||i, ciphertext, tag, aad = file_id||i}Límites y advertencias
- No reutilices el mismo nonce para dos chunks con la misma DEK.
- Define tamaño de chunk para equilibrar memoria/latencia (p. ej., 1–8 MiB).
- Verifica tags por chunk antes de exponer datos; si falla un chunk, trata el archivo como corrupto.
Secretos de aplicación (config, API keys, credenciales internas)
Si necesitas cifrar secretos en reposo (p. ej., en una tabla de configuración), AEAD funciona bien, pero hay una recomendación operativa clave: si puedes, delega en un gestor de secretos (KMS + Secret Manager/Vault) y evita construir tu propio sistema de distribución.
Patrón práctico
- Usa una clave de cifrado gestionada (KMS/HSM) o una KEK rotada.
- Incluye en AAD el nombre del secreto y el entorno:
"secret=db_password|env=prod". - Aplica control de acceso estricto: el cifrado no sustituye autorización.
Límites y advertencias
- Evita descifrar secretos en más servicios de los necesarios (principio de mínimo privilegio).
- No registres plaintext ni envelopes completos en logs.
- Considera el ciclo de vida: rotación del secreto y rotación de la clave son procesos distintos.
Checklist de producción (rápida y accionable)
- Usas AEAD (AES-GCM o ChaCha20-Poly1305) con bibliotecas estándar.
- Nonce de 12 bytes y único por clave (estrategia definida y testeada).
- AAD cubre el contexto (tenant/campo/recurso) para evitar replays/cross-context.
- Envelope con
version+kid+alg+nonce+ciphertext+tag. - Errores genéricos; no hay oráculos por mensajes o logs expuestos.
- Rotación: clave de escritura + claves de lectura; re-cifrado gradual.
- Pruebas: casos de nonce repetido (debe detectarse por diseño), tag inválido, AAD distinta, versión desconocida.