Elegir entre AES-GCM y ChaCha20-Poly1305 es elegir entre los dos algoritmos de cifrado autenticado que transportan casi todo el tráfico cifrado: TLS 1.3, QUIC, SSH, WireGuard, age y la mayor parte del cifrado a nivel de aplicación. Ambos son seguros si se usan correctamente, ambos fallan de forma grave cuando se repite un nonce, y su velocidad depende mucho del hardware.
De un vistazo
| AES-GCM | ChaCha20-Poly1305 | |
|---|---|---|
| Especificación | NIST SP 800-38D (2007) | RFC 8439 (2018, sustituye al RFC 7539) |
| Tamaños de clave | 128, 192, 256 bits | 256 bits |
| Nonce | 96 bits recomendados | 96 bits (192 bits en XChaCha20) |
| Etiqueta | 128 bits (se admiten etiquetas más cortas, pero no se recomiendan) | 128 bits |
| Máximo de datos por mensaje | unos 64 GiB (2³² − 2 bloques) | 256 GiB (2³² bloques de 64 bytes) |
| Presupuesto de nonces aleatorios por clave | 2³² mensajes (SP 800-38D) | Del mismo orden con nonces de 96 bits; prácticamente ilimitado con XChaCha20 |
| Reutilización del nonce | Revela el XOR de los textos en claro y la clave de GHASH, lo que permite falsificaciones | Revela el XOR de los textos en claro y permite falsificaciones |
| Rendimiento, 16 KiB, OpenSSL 3.6.5 | 10,4 GB/s (AES-128), 8,8 GB/s (AES-256) | 2,1 GB/s |
| Rendimiento, mensajes de 16 bytes | 0,14 GB/s (AES-256) | 0,52 GB/s |
| Tiempo constante sin hardware | Difícil (el AES basado en tablas filtra información a través de la caché) | Natural (sumas, rotaciones y XOR) |
| Aprobado en FIPS 140-3 | Sí | No |
| TLS 1.3 | TLS_AES_128_GCM_SHA256 (MUST), TLS_AES_256_GCM_SHA384 (SHOULD) | TLS_CHACHA20_POLY1305_SHA256 (SHOULD) |
| Otros usos | JWE A128GCM y A256GCM, IPsec, SSH aes256-gcm@openssh.com, cifrado de discos y en la nube | WireGuard, SSH chacha20-poly1305@openssh.com, age, libsodium |
Rendimiento medido con openssl speed -evp en un núcleo de un Apple M4 Max, que tiene instrucciones de AES y de multiplicación polinómica.
El rendimiento depende de la CPU
AES-GCM está pensado para el hardware: cada ronda de AES se ejecuta como una sola instrucción (AES-NI en x86, las extensiones criptográficas de ARMv8) y GHASH usa multiplicación sin acarreo. Con ambas disponibles, aquí alcanza unos 10 GB/s por núcleo, unas cinco veces más que ChaCha20-Poly1305. Sin ellas, AES tiene que recurrir a tablas de consulta, que son lentas y filtran bits de la clave a través de los tiempos de la caché, o a código bitsliced, que es de tiempo constante pero lento. ChaCha20 solo necesita sumas, rotaciones y XOR de 32 bits, así que se ejecuta rápido y en tiempo constante en cualquier CPU, incluidos los teléfonos de gama baja y los microcontroladores.
Con mensajes pequeños, la diferencia se reduce o se invierte: con entradas de 16 bytes, ChaCha20-Poly1305 alcanzó 0,52 GB/s frente a 0,14 GB/s de AES-256-GCM, porque domina el coste de preparación de GCM. Con los registros TLS habituales (de hasta 16 KiB) en servidores, AES-GCM es más rápido; con muchos paquetes diminutos, la diferencia es pequeña en cualquier caso.
Nonces y límites
Ambos son algoritmos de cifrado en modo contador con autenticadores de un solo uso, así que el par (clave, nonce) nunca debe repetirse. Una repetición revela el XOR de los dos textos en claro; con GCM, además, permite a un atacante recuperar la subclave de autenticación y falsificar mensajes con esa clave, y con Poly1305 expone la clave de un solo uso y permite falsificar mensajes para ese nonce. NIST SP 800-38D limita los IV de 96 bits generados aleatoriamente a 2³² invocaciones por clave para que la probabilidad de colisión sea despreciable; TLS esquiva el problema derivando los nonces de los números de secuencia de los registros y renovando las claves.
Si tu diseño genera nonces aleatorios para muchos mensajes con una misma clave, usa una construcción con más espacio de nonces o con resistencia al mal uso:
- XChaCha20-Poly1305 (nonces de 192 bits, descrito en un Internet-Draft del IRTF e implementado por libsodium, entre otros), con el que los nonces aleatorios son seguros para una cantidad prácticamente ilimitada de mensajes;
- AES-GCM-SIV (RFC 8452), con el que la reutilización del nonce solo revela si dos mensajes eran idénticos;
- o la derivación de claves por mensaje o por sesión, como hacen TLS, age y HPKE.
Ninguno de los dos algoritmos ofrece compromiso con la clave: se puede construir un texto cifrado que se descifre de forma válida con dos claves distintas, algo que importa en diseños con varios destinatarios o basados en contraseñas (el problema conocido como «invisible salamanders»). Si los atacantes pueden elegir las claves, añade un compromiso con la clave (key commitment).
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
const plaintext = Buffer.from("card ending 4242, amount 19.99");
const aad = Buffer.from("order=7781");
for (const algorithm of ["aes-256-gcm", "chacha20-poly1305"]) {
const key = randomBytes(32);
const nonce = randomBytes(12); // 96-bit nonce for both; never reuse with the same key
const cipher = createCipheriv(algorithm, key, nonce, { authTagLength: 16 });
cipher.setAAD(aad);
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
const decipher = createDecipheriv(algorithm, key, nonce, { authTagLength: 16 });
decipher.setAAD(aad);
decipher.setAuthTag(tag);
const roundTrip = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
console.log(algorithm.padEnd(18), "ciphertext", ciphertext.length, "bytes + tag", tag.length, "bytes, decrypts:", roundTrip.equals(plaintext));
}
aes-256-gcm ciphertext 30 bytes + tag 16 bytes, decrypts: true
chacha20-poly1305 ciphertext 30 bytes + tag 16 bytes, decrypts: true
Cómo elige TLS entre ellos
TLS 1.3 exige que toda implementación admita TLS_AES_128_GCM_SHA256 y recomienda AES-256-GCM y ChaCha20-Poly1305. Los clientes sin hardware AES ponen ChaCha20 en primer lugar; los servidores que respetan esa preferencia sirven a cada cliente el cifrado que le resulta más rápido. OpenSSL lo implementa con la opción PrioritizeChaCha (SSL_OP_PRIORITIZE_CHACHA), que prefiere ChaCha20 solo cuando el cliente lo puso en primer lugar, y BoringSSL usa grupos de igual preferencia con el mismo fin. El generador de configuración TLS produce una configuración de servidor que mantiene ambos conjuntos de cifrado.
Cuándo elegir cada uno
- Servidores TLS y QUIC: activa ambos; prefiere AES-GCM, pero deja que los clientes que ponen ChaCha20 en primer lugar lo obtengan.
- Entornos FIPS 140-3: AES-GCM; ChaCha20-Poly1305 no es un algoritmo aprobado.
- Dispositivos móviles, IoT y microcontroladores sin aceleración AES: ChaCha20-Poly1305.
- Cifrado a nivel de aplicación con nonces aleatorios y claves de larga duración: XChaCha20-Poly1305 o AES-GCM-SIV.
- JWE:
A256GCM(oA128GCM); ChaCha20 no figura en el registro principal de JWA. - Mensajes individuales muy grandes (más de 64 GiB): divídelos en fragmentos con un nonce por fragmento (como hace age con fragmentos de ChaCha20-Poly1305 de 64 KiB); no te apoyes en los límites por mensaje.
Notas de migración
Cambiar de AEAD modifica el formato del texto cifrado, pero nada más en un sistema bien diseñado.
- Versiona el formato: guarda un identificador de algoritmo y un identificador de clave con cada texto cifrado para que los datos antiguos sigan siendo legibles.
- Usa una clave distinta por algoritmo: deriva claves diferentes (por ejemplo, con HKDF y cadenas de info distintas) en lugar de reutilizar una misma clave para ambos.
- Revisa la generación de nonces: los contadores deben persistir entre reinicios; los nonces aleatorios deben mantenerse dentro de los límites anteriores o pasar a XChaCha20.
- Vuelve a cifrar de forma diferida: descifra con el algoritmo antiguo y escribe con el nuevo a medida que se accede a los registros, salvo que un plazo de cumplimiento normativo exija una migración masiva.
- Mide el rendimiento en el hardware real: el orden de velocidad se invierte entre las CPU de servidor y los dispositivos pequeños.