Respuesta breve. X25519MLKEM768 es un grupo de intercambio de claves de TLS 1.3, con el punto de código 0x11EC (4588), que ejecuta ML-KEM-768 y X25519 en paralelo y lleva los dos secretos compartidos al key schedule (la fase que deriva las claves a partir de esos secretos). Está estandarizado en el RFC 10024 (agosto de 2026). Chrome, Firefox, las API de red de Apple en las versiones 26, OpenSSL 3.5, Go 1.24+ y JDK 27 lo ofrecen por defecto. Ejecuta openssl s_client -connect example.com:443 -brief con OpenSSL 3.5 y busca Negotiated TLS1.3 group: X25519MLKEM768. Protege el tráfico grabado frente a una futura computadora cuántica. No hace nada por la autenticación, que sigue dependiendo de certificados RSA o ECDSA.
Qué dice el nombre
TLS 1.3 negocia el intercambio de claves mediante grupos con nombre (named groups). El cliente enumera los grupos que admite en la extensión supported_groups y envía claves públicas para uno o varios de ellos en key_share. El servidor elige un grupo y responde con su propio key share. Durante años, las opciones realistas fueron X25519 y ECDH sobre P-256. Las dos caen ante el algoritmo de Shor en una computadora cuántica lo bastante grande, y el tráfico grabado hoy podrá descifrarse en cuanto exista una máquina así. Es el problema de «cosechar ahora, descifrar después» (harvest now, decrypt later).
Un grupo híbrido lo resuelve sin apostarlo todo a unas matemáticas nuevas. X25519MLKEM768 ejecuta dos acuerdos de claves en una sola negociación:
- ML-KEM-768, el mecanismo de encapsulación de claves basado en retículos de módulo de FIPS 203 (agosto de 2024), con parámetros de categoría 3. Esta parte resiste los ataques cuánticos.
- X25519, la función Diffie-Hellman de curva elíptica del RFC 7748, que tiene un largo historial de criptoanálisis y sirve de respaldo al componente más nuevo.
La construcción genérica se describe en el RFC 9954 (Intercambio de claves híbrido en TLS 1.3, julio de 2026), cuyo objetivo declarado es la seguridad «incluso si se encuentra una forma de romper el cifrado de todos los algoritmos componentes salvo uno». Los grupos concretos se definen en el RFC 10024 (agosto de 2026):
| Grupo | Punto de código | Recomendado | Componentes |
|---|---|---|---|
| X25519MLKEM768 | 0x11EC (4588) | Sí | ML-KEM-768 + X25519 |
| SecP256r1MLKEM768 | 0x11EB (4587) | No | ECDH sobre P-256 + ML-KEM-768 |
| SecP384r1MLKEM1024 | 0x11ED (4589) | No | ECDH sobre P-384 + ML-KEM-1024 |
Las capturas más antiguas pueden mostrar X25519Kyber768Draft00, con el punto de código 0x6399 (25497). Era el híbrido de Kyber previo al estándar, el que Chrome distribuyó primero. FIPS 203 cambió el algoritmo en detalles pequeños pero incompatibles, así que Chrome 131 lo sustituyó por 0x11EC y retiró el grupo del borrador. El anuncio de Google explica por qué no admitió los dos: «La criptografía poscuántica es demasiado grande como para poder ofrecer dos predicciones de key share poscuánticas al mismo tiempo». El RFC 10024 marca como obsoletos los puntos de código del borrador.
Dentro del key share
El key share híbrido es una simple concatenación de los valores de los dos componentes, sin prefijos de longitud en su interior. FIPS 203 y el RFC 7748 fijan los tamaños, así que cada campo puede comprobarse solo por su longitud:
| Campo | Disposición | Bytes |
|---|---|---|
key_exchange del cliente | Clave de encapsulación ML-KEM-768 (1184) ‖ clave pública X25519 (32) | 1216 |
key_exchange del servidor | Texto cifrado ML-KEM-768 (1088) ‖ clave pública X25519 (32) | 1120 |
| Secreto compartido | Secreto compartido ML-KEM (32) ‖ secreto compartido X25519 (32) | 64 |
Fíjate en el orden. El nombre empieza por X25519, pero los bytes empiezan por ML-KEM. El RFC 10024 dice que el orden «se ha invertido» respecto a la convención de nombres «por razones históricas». Su sección 5 da además un motivo de cumplimiento normativo: NIST SP 800-56C solo admite un secreto combinado en una derivación de claves aprobada por FIPS cuando la primera entrada procede de un esquema aprobado, y en este par ese esquema es ML-KEM. Los dos híbridos con curvas NIST ponen primero el key share ECDH por el mismo motivo. Un analizador que divida el campo en el byte 32 en lugar del byte 1184 fallará siempre.
Este es un ClientHello real de OpenSSL 3.5.7 a cloudflare.com, capturado con -trace el 20 de octubre de 2026 (con los bytes de las claves acortados):
Sent TLS Record
Content Type = Handshake (22)
Length = 1551
ClientHello, Length=1547
extension_type=supported_groups(10), length=18
X25519MLKEM768 (4588)
ecdh_x25519 (29)
secp256r1 (P-256) (23)
ecdh_x448 (30)
secp384r1 (P-384) (24)
secp521r1 (P-521) (25)
ffdhe2048 (256)
ffdhe3072 (257)
extension_type=key_share(51), length=1258
NamedGroup: X25519MLKEM768 (4588)
key_exchange: (len=1216): A4696B297C695DEA602FB55157BC359F...
NamedGroup: ecdh_x25519 (29)
key_exchange: (len=32): 919D957E3D7E402ECF27C3DD0B38DDA0...
El cliente envía dos key shares: el híbrido y uno de X25519 a secas. Es el comportamiento predeterminado de OpenSSL 3.5. Go 1.27.3 envía el mismo par, y también Chromium 156, que añade una entrada GREASE y llega como un registro de 1704 bytes. JEP 527 especifica el mismo par para JDK 27. El key share de X25519 a secas cuesta 32 bytes y permite que un servidor que no conoce 0x11EC complete el handshake en un solo viaje de ida y vuelta, en lugar de pedir otro key share. La extensión key_share cuadra exactamente: 2 bytes de longitud de la lista, luego 2 + 2 + 1216 para la entrada híbrida y 2 + 2 + 32 para X25519, 1258 en total. El servidor eligió el híbrido:
ServerHello, Length=1206
extension_type=key_share(51), length=1124
NamedGroup: X25519MLKEM768 (4588)
key_exchange: (len=1120): C01BC77BB1F9985B0DE82CDD1D0DD14B...
Para comparar, el mismo cliente con -groups X25519 envía un ClientHello de 313 bytes. El híbrido añade aproximadamente 1,2 KB en cada sentido, y ese tamaño es el origen de la mayoría de los problemas de compatibilidad (ver más abajo).
De dos secretos a un solo key schedule
Cada lado realiza dos operaciones independientes:
- El cliente genera un par de claves ML-KEM-768 y un par de claves X25519 y envía las dos claves públicas.
- El servidor encapsula con la clave ML-KEM. Obtiene un secreto de 32 bytes y un texto cifrado de 1088 bytes. También ejecuta X25519 con su propia clave efímera y después envía el texto cifrado y su clave pública X25519.
- El cliente desencapsula el texto cifrado para recuperar el mismo secreto ML-KEM y completa X25519 por su parte.
- Los dos lados concatenan los resultados en 64 bytes y usan ese valor donde TLS 1.3 pone normalmente el secreto (EC)DHE: como entrada del HKDF-Extract que deriva el secreto del handshake (RFC 9846 §7.1, la revisión de TLS 1.3 de julio de 2026 que deja obsoleto el RFC 8446).
Todo lo que viene después es TLS 1.3 normal. Los conjuntos de cifrado, el certificado, CertificateVerify y las claves de tráfico no cambian. El RFC 10024 añade pasos de validación que las implementaciones deben realizar: comprueban la clave de encapsulación como exige FIPS 203, rechazan un texto cifrado de longitud incorrecta y abortan si el resultado de X25519 es todo ceros. Cada fallo termina el handshake con una alerta illegal_parameter o internal_error.
El argumento de seguridad es breve. Un atacante que graba el handshake tiene que recuperar los dos secretos para calcular el secreto del handshake. Romper X25519 con una futura computadora cuántica no basta mientras ML-KEM resista. Una sorpresa criptoanalítica contra el joven esquema de retículos no basta mientras X25519 resista. Si quieres ver la encapsulación y la desencapsulación con claves reales, el laboratorio de ML-KEM las ejecuta en tu navegador.
Quién lo activa por defecto
En octubre de 2026, todos los clientes principales lo ofrecen por defecto:
| Software | Por defecto desde | Fuente |
|---|---|---|
| Chrome (escritorio) | Chrome 131, que sustituye al grupo del borrador de Kyber | Google Security Blog |
| Firefox | Compatible desde Firefox 132 (TLS) y Firefox 135 (HTTP/3). La preferencia security.tls.enable_kyber vale true por defecto | Notas de la versión |
| Apple: URLSession y el framework Network | Las versiones 26: iOS, iPadOS, macOS Tahoe, visionOS (guía de red), además de tvOS y watchOS (guía de seguridad) | Soporte de Apple, Seguridad de las plataformas |
| OpenSSL | 3.5.0: grupos híbridos en la lista predeterminada, key shares predeterminados X25519MLKEM768 + X25519 | NEWS.md |
Go crypto/tls | Go 1.24, cuando Config.CurvePreferences es nil. Go 1.26 añade los dos híbridos con curvas NIST | Go 1.24, Go 1.26 |
| Java (JSSE) | JDK 27, JEP 527: X25519MLKEM768 en primer lugar de la lista predeterminada | JEP 527 |
Todo lo que está enlazado con OpenSSL 3.5 también hereda el valor predeterminado, siempre que su configuración no sustituya la lista de grupos. Eso abarca los paquetes de Debian 13 y Node.js 24, que trae su propio OpenSSL 3.5. En Debian 13, el módulo ssl de Python completó un handshake con un openssl s_server -groups X25519MLKEM768 que no acepta nada más, y curl y nginx negociaron el híbrido sin configuración adicional. La página Seguridad de TLS de Apple señala una laguna: «los clientes TLS que usan las API de SecureTransport no pueden usar TLS 1.3», así que las aplicaciones que siguen con esa API se quedan sin intercambio de claves híbrido.
En el lado del servidor, una gran parte del tráfico HTTPS ya termina en pilas que eligen el híbrido cuando el cliente lo ofrece. Una prueba desde OpenSSL 3.5.7 el 20 de octubre de 2026 negoció X25519MLKEM768 con cloudflare.com, google.com, www.amazon.com, www.microsoft.com, www.apple.com y example.com, y X25519 a secas con github.com y letsencrypt.org.
Cómo comprobar un servidor, un cliente o un navegador
Desde la línea de comandos. La salida -brief de OpenSSL 3.5 nombra el grupo directamente:
$ openssl s_client -connect cloudflare.com:443 -servername cloudflare.com -brief </dev/null
Connecting to 104.16.132.229
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=cloudflare.com
Hash used: SHA256
Signature type: ecdsa_secp256r1_sha256
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE
Frente a un servidor que elige un grupo clásico, la salida muestra en su lugar Peer Temp Key: X25519, 253 bits. Para saber si un servidor admite el híbrido, no ofrezcas nada más. Con un servidor solo clásico, el handshake falla:
$ openssl s_client -groups X25519MLKEM768 -connect github.com:443 -servername github.com -brief </dev/null
...:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:...:SSL alert number 40
Usa ese comando solo para pruebas. Un cliente real que no ofrece nada más que el híbrido renuncia a la compatibilidad sin ganar nada. curl compilado con OpenSSL 3.5 imprime el grupo en la línea de conexión de su salida detallada:
$ curl -sv -o /dev/null https://www.google.com/ 2>&1 | grep 'SSL connection'
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / id-ecPublicKey
Desde el código. Go 1.25 y posteriores exponen el grupo negociado como ConnectionState().CurveID, y Node.js 24 lo devuelve con getEphemeralKeyInfo():
conn, err := tls.Dial("tcp", "cloudflare.com:443", &tls.Config{ServerName: "cloudflare.com"})
if err != nil {
log.Fatal(err)
}
fmt.Println(conn.ConnectionState().CurveID) // X25519MLKEM768 (Go 1.27.3)
import tls from "node:tls";
const socket = tls.connect({ host: "cloudflare.com", port: 443, servername: "cloudflare.com" }, () => {
console.log(socket.getEphemeralKeyInfo()); // { type: 'TLSGroup', name: 'X25519MLKEM768', size: undefined } (Node 24.22.0)
socket.end();
});
En el navegador. Chrome muestra el grupo en DevTools, en los detalles de la conexión del panel de seguridad. El mismo valor se puede obtener desde código como securityDetails.keyExchangeGroup en el evento Network.responseReceived del protocolo de DevTools. Chromium 156 en modo headless devolvió X25519MLKEM768 para www.cloudflare.com y X25519 para github.com el 20 de octubre de 2026. En macOS 26, Apple sugiere nscurl --tls-diagnostics https://your.host. Para ver qué envía tu navegador, abre la comprobación de TLS poscuántico del navegador.
Cómo activarlo en tu servidor
Con OpenSSL 3.5 por debajo, el valor predeterminado ya es el correcto. La forma habitual de perderlo es una lista explícita de grupos copiada de una guía antigua de refuerzo de la seguridad. Esta prueba con nginx 1.30.5 en Debian 13 (OpenSSL 3.5.7) cambia solo la línea ssl_ecdh_curve y comprueba el resultado con openssl s_client -brief:
--- no ssl_ecdh_curve (OpenSSL default list)
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768
--- ssl_ecdh_curve X25519:prime256v1:secp384r1;
Protocol version: TLSv1.3
Peer Temp Key: X25519, 253 bits
--- ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768
La solución, por tanto, es una de estas: quitar la línea o poner X25519MLKEM768 en primer lugar en ella. Las versiones antiguas de OpenSSL no conocen ese nombre, así que actualiza primero la biblioteca TLS. nginx 1.24.0 con OpenSSL 1.1.1w rechaza una línea así (en este caso ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;) con nginx: [emerg] SSL_CTX_set1_curves_list("X25519MLKEM768:X25519:prime256v1") failed. Otros servidores siguen el mismo patrón con otros nombres: SSLOpenSSLConfCmd Groups en Apache httpd, curves en HAProxy, CurvePreferences en Go, jdk.tls.namedGroups en Java. Si necesitas un punto de partida para un servidor concreto, el generador de configuración TLS crea configuraciones con los grupos híbridos ya incluidos.
Hay dos sitios más que es fácil olvidar:
- El origen detrás de una CDN o un balanceador de carga. La conexión del navegador termina en el borde (edge), y la conexión del borde con tu origen es una sesión TLS independiente, con su propia negociación. El tráfico grabado en ese segundo tramo puede cosecharse exactamente igual.
- TLS interno entre servicios. Los servicios en Go obtienen el híbrido según la versión de Go con la que se compilan. Los servicios en Java lo obtienen de JDK 27, donde JEP 527 añadió los grupos híbridos. Todo lo que fija
CurvePreferences,jdk.tls.namedGroupsu otra lista explícita de grupos se queda sin nada hasta que alguien la modifique.
Predicción del key share y HelloRetryRequest
Un cliente TLS 1.3 tiene que adivinar qué grupo elegirá el servidor y enviar desde el principio un key share para ese grupo. Si se equivoca, el servidor responde con un HelloRetryRequest que indica el grupo que quiere, y el cliente vuelve a empezar. Eso cuesta un viaje de ida y vuelta completo. La apuesta importa más con los key shares poscuánticos, porque enviar un key share de 1216 bytes «por si acaso» no sale gratis. Puedes ver el reintento si le pides a OpenSSL que incluya el híbrido en la lista pero envíe un key share solo para X25519:
ClientHello, Length=315
extension_type=key_share(51), length=38
NamedGroup: ecdh_x25519 (29)
ServerHello, Length=84 <- HelloRetryRequest
extension_type=key_share(51), length=2
NamedGroup: X25519MLKEM768 (4588)
ClientHello, Length=1499
extension_type=key_share(51), length=1222
NamedGroup: X25519MLKEM768 (4588)
ServerHello, Length=1206
Negotiated TLS1.3 group: X25519MLKEM768
El servidor prefirió el híbrido aunque el cliente puso X25519 primero en la lista, así que pidió un reintento. En las cadenas de grupos de OpenSSL 3.5, el prefijo * marca los grupos que reciben un key share ya en el primer ClientHello. -groups '*X25519:*X25519MLKEM768' envía los dos key shares, en un ClientHello de 1535 bytes, y evita el reintento. Los valores predeterminados de los clientes principales envían los dos por el mismo motivo. Un borrador del grupo de trabajo TLS, draft-ietf-tls-key-share-prediction, permite que los servidores publiquen sus grupos preferidos en registros DNS HTTPS para que los clientes acierten a la primera. En octubre de 2026 todavía era un Internet-Draft.
Qué se rompe: ClientHellos grandes y dispositivos intermedios antiguos
El ClientHello medido más arriba llena un registro TLS con 1551 bytes de carga útil. Con los encabezados del registro, de TCP y de IP, ya no cabe en un solo paquete en una ruta típica con una MTU de 1500 bytes. La especificación siempre lo ha permitido, pero algunas implementaciones daban por hecho que el primer read() del socket devuelve el ClientHello completo. Fallan cuando el híbrido lo reparte en dos segmentos.
El proyecto tldr.fail sigue esta clase de errores. Describe servidores «no preparados para tener que llamar a read() más de una vez para leer el ClientHello completo». Su lista incluye proxies que inspeccionan TLS, cortafuegos y balanceadores de carga de varios fabricantes. Algunos se han corregido desde entonces y otros figuran con estado desconocido. Los síntomas parecen problemas de red, no de criptografía: ERR_CONNECTION_RESET o ERR_SSL_PROTOCOL_ERROR en Chrome, tiempos de espera agotados en el handshake o conexiones reiniciadas justo después del ClientHello. A menudo el error solo se ve desde clientes que envían el híbrido. Apple advierte del mismo fallo en sus notas para las versiones 26: los dispositivos podrían no conectar con algunos servidores antiguos cuyo código TLS «no consigue leer mensajes ClientHello grandes».
Si te ocurre:
- Encuentra el dispositivo que reinicia la conexión. Compara
openssl s_client -groups X25519con la lista de grupos predeterminada a través de la misma ruta. Si solo falla el ClientHello grande, el problema es el tamaño, no el certificado. - Aplica un parche a ese dispositivo o sustitúyelo. Desactivar el híbrido en los clientes elimina la protección de todas las conexiones, incluidas las que funcionaban.
- Usa los ajustes del lado del cliente solo como medida provisional. Go tiene
GODEBUG=tlsmlkem=0, y las notas de Go 1.24 lo mencionan para «servidores TLS con errores que no gestionan correctamente los registros grandes». Chrome tenía la política empresarialPostQuantumKeyAgreementEnabled, que Google describió como «una medida temporal» que «se eliminará en algún momento después de la versión 145 de Google Chrome». Comprueba que tu versión de Chrome todavía la respeta antes de hacer planes que dependan de ella.
Los proxies que terminan TLS merecen una nota más. Un proxy que descifra el tráfico realiza dos handshakes. Lo que tu equipo negocie con el proxy no dice nada de la conexión del proxy con el exterior. Ese tramo solo es poscuántico si la propia pila TLS del proxy admite y prefiere el híbrido.
Lo que no protege
X25519MLKEM768 protege la confidencialidad frente a un adversario que graba el tráfico ahora y lo descifra más tarde. No hace poscuántica la autenticación del handshake. El servidor sigue demostrando su identidad con un certificado RSA o ECDSA y una firma CertificateVerify. Alguien capaz de falsificar esas firmas con una computadora cuántica podría suplantar a un servidor en una conexión en vivo. Esa amenaza solo existirá cuando exista una computadora así, y por eso el intercambio de claves va primero y las firmas son una migración aparte y más lenta. Los demás artículos de esta serie cubren ese trabajo: las claves y firmas ML-DSA en ML-DSA para desarrolladores, y el plan general, con el inventario y los plazos, en Migración poscuántica en 2026.
Tampoco hace nada por el tráfico que no es TLS 1.3. Las conexiones TLS 1.2, las sesiones SSH y los túneles VPN tienen sus propios intercambios de claves y sus propias vías de actualización. Averigua cuáles de tus conexiones siguen negociando TLS 1.2 antes de contarlas como protegidas.
Fuentes
- RFC 10024: Mecanismos híbridos de acuerdo de claves poscuánticos y tradicionales (PQ/T) para TLS 1.3 (agosto de 2026). Definiciones de los grupos, orden de los bytes, validación, puntos de código de la IANA.
- RFC 9954: Intercambio de claves híbrido en TLS 1.3 (julio de 2026).
- FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard, el estándar del mecanismo de encapsulación de claves basado en retículos de módulo (agosto de 2024).
- RFC 9846 §7.1: el key schedule de TLS 1.3.
- Google Security Blog, Un nuevo camino para Kyber en la web (septiembre de 2024).
- Soporte de Apple, Prepara tu red para el cifrado con seguridad cuántica en TLS, y la guía de seguridad de las plataformas de Apple.
- NEWS.md de OpenSSL 3.5, notas de las versiones 1.24 y 1.26 de Go, JEP 527.
- tldr.fail: el error de los ClientHello grandes y los productos afectados.
Toda la salida de comandos de esta página procede de contenedores ejecutados el 20 de octubre de 2026: OpenSSL 3.5.7, curl 8.14.1, Node.js 24.22.0 y el Python 3 de Debian en node:24.22.0-trixie, Go 1.27.3 en golang:1.27.3-trixie, nginx 1.30.5 en nginx:1.30.5, nginx 1.24.0 con OpenSSL 1.1.1w en nginx:1.24.0 y Chromium 156 en mcr.microsoft.com/playwright:v1.64.1-noble.