Nota
Implementación, pruebas y despliegue en progreso en las diversas implementaciones de router. Consulta la documentación de esas implementaciones para conocer el estado.
Resumen
Esta es la variante PQ Hybrid del protocolo ECIES-X25519-AEAD-Ratchet ECIES . Es la primera fase de la propuesta general PQ Prop169 que será aprobada. Consulte esa propuesta para objetivos generales, modelos de amenaza, análisis, alternativas e información adicional.
Esta especificación contiene únicamente las diferencias del ECIES estándar y debe leerse junto con esa especificación.
Diseño
Utilizamos el estándar NIST FIPS 203 FIPS203 que está basado en, pero no es compatible con, CRYSTALS-Kyber (versiones 3.1, 3 y anteriores).
Los handshakes híbridos están especificados según Noise-Hybrid .
Intercambio de Claves
Definimos un intercambio de claves híbrido para Ratchet. PQ KEM proporciona únicamente claves efímeras y no admite directamente intercambios de claves estáticas como Noise IK.
Definimos las tres variantes de ML-KEM como en FIPS203 , para un total de 3 nuevos tipos de cifrado. Los tipos híbridos solo se definen en combinación con X25519.
Los nuevos tipos de cifrado son:
| Type | Code |
|---|---|
| MLKEM512_X25519 | 5 |
| MLKEM768_X25519 | 6 |
| MLKEM1024_X25519 | 7 |
Nueva Criptografía Requerida
- ML-KEM (anteriormente CRYSTALS-Kyber) FIPS203
- SHA3-128 (anteriormente Keccak-256) FIPS202 Usado únicamente para SHAKE128
- SHA3-256 (anteriormente Keccak-512) FIPS202
- SHAKE128 y SHAKE256 (extensiones XOF para SHA3-128 y SHA3-256) FIPS202
Los vectores de prueba para SHA3-256, SHAKE128 y SHAKE256 están en NIST-VECTORS .
Ten en cuenta que la biblioteca Java bouncycastle soporta todo lo anterior. El soporte de la biblioteca C++ está en OpenSSL 3.5 OPENSSL .
Especificación
Estructuras Comunes
Consulta la especificación de estructuras comunes COMMON para longitudes de clave e identificadores.
Patrones de Handshake
Los handshakes utilizan patrones de handshake Noise .
Se utiliza el siguiente mapeo de letras:
- e = clave efímera de un solo uso
- s = clave estática
- p = carga útil del mensaje
- e1 = clave PQ efímera de un solo uso, enviada de Alice a Bob
- ekem1 = el texto cifrado KEM, enviado de Bob a Alice
Las siguientes modificaciones a XK e IK para secreto hacia adelante híbrido (hfs) están especificadas en Noise-Hybrid sección 5:
IK: IKhfs:
<- s <- s
... ...
-> e, es, s, ss, p -> e, es, e1, s, ss, p
<- tag, e, ee, se, p <- tag, e, ee, ekem1, se, p
<- p <- p
p -> p ->
e1 and ekem1 are encrypted. See pattern definitions below.
NOTE: e1 and ekem1 are different sizes (unlike X25519)
El patrón e1 se define de la siguiente manera, como se especifica en Noise-Hybrid sección 4:
For Alice:
(encap_key, decap_key) = PQ_KEYGEN()
// EncryptAndHash(encap_key)
ciphertext = ENCRYPT(k, n, encap_key, ad)
n++
MixHash(ciphertext)
For Bob:
// DecryptAndHash(ciphertext)
encap_key = DECRYPT(k, n, ciphertext, ad)
n++
MixHash(ciphertext)
El patrón ekem1 se define de la siguiente manera, según se especifica en Noise-Hybrid sección 4:
For Bob:
(kem_ciphertext, kem_shared_key) = ENCAPS(encap_key)
// EncryptAndHash(kem_ciphertext)
ciphertext = ENCRYPT(k, n, kem_ciphertext, ad)
MixHash(ciphertext)
// MixKey
MixKey(kem_shared_key)
For Alice:
// DecryptAndHash(ciphertext)
kem_ciphertext = DECRYPT(k, n, ciphertext, ad)
MixHash(ciphertext)
// MixKey
kem_shared_key = DECAPS(kem_ciphertext, decap_key)
MixKey(kem_shared_key)
Operaciones ML-KEM Definidas
Definimos las siguientes funciones correspondientes a los bloques de construcción criptográficos utilizados como se define en FIPS203 .
(encap_key, decap_key) = PQ_KEYGEN()
Alice crea las claves de encapsulación y desencapsulación. La clave de encapsulación se envía en el mensaje NS. Los tamaños de encap_key y decap_key varían según la variante ML-KEM.
(ciphertext, kem_shared_key) = ENCAPS(encap_key)
Bob calcula el texto cifrado y la clave compartida, usando el texto cifrado recibido en el mensaje NS. El texto cifrado se envía en el mensaje NSR. El tamaño del texto cifrado varía según la variante ML-KEM. La kem_shared_key siempre es de 32 bytes.
kem_shared_key = DECAPS(ciphertext, decap_key)
Alice calcula la clave compartida, utilizando el texto cifrado recibido en el mensaje NSR. La kem_shared_key siempre tiene 32 bytes.
Tenga en cuenta que tanto el encap_key como el texto cifrado están encriptados dentro de bloques ChaCha/Poly en los mensajes 1 y 2 del handshake de Noise. Serán descifrados como parte del proceso de handshake.
El kem_shared_key se mezcla en la clave de encadenamiento con MixHash(). Ver detalles a continuación.
KDF de Handshake Noise
Resumen
El handshake híbrido se define en Noise-Hybrid . El primer mensaje, de Alice a Bob, contiene e1, la clave de encapsulación, antes de la carga útil del mensaje. Esto se trata como una clave estática adicional; llama a EncryptAndHash() en ella (como Alice) o DecryptAndHash() (como Bob). Luego procesa la carga útil del mensaje como de costumbre.
El segundo mensaje, de Bob a Alice, contiene ekem1, el texto cifrado, antes de la carga útil del mensaje. Esto se trata como una clave estática adicional; llama a EncryptAndHash() en ella (como Bob) o DecryptAndHash() (como Alice). Luego, calcula el kem_shared_key y llama a MixKey(kem_shared_key). Después procesa la carga útil del mensaje como de costumbre.
Identificadores de ruido
Estas son las cadenas de inicialización de Noise:
- “Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256”
KDF de Alice para Mensaje NS
Después del patrón de mensaje ’es’ y antes del patrón de mensaje ’s’, añadir:
This is the "e1" message pattern:
(encap_key, decap_key) = PQ_KEYGEN()
// EncryptAndHash(encap_key)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, encap_key, ad)
n++
// MixHash(ciphertext)
h = SHA256(h || ciphertext)
End of "e1" message pattern.
NOTE: For the next section (payload for XK or static key for IK),
the keydata and chain key remain the same, and n now equals 1
(instead of 0 for non-hybrid).
KDF de Bob para Mensaje NS
Después del patrón de mensaje ’es’ y antes del patrón de mensaje ’s’, añadir:
This is the "e1" message pattern:
// DecryptAndHash(encap_key_section)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
encap_key = DECRYPT(k, n, encap_key_section, ad)
n++
// MixHash(encap_key_section)
h = SHA256(h || encap_key_section)
End of "e1" message pattern.
NOTE: For the next section (payload for XK or static key for IK),
the keydata and chain key remain the same, and n now equals 1
(instead of 0 for non-hybrid).
KDF de Bob para Mensaje NSR
Después del patrón de mensaje ’ee’ y antes del patrón de mensaje ‘se’, añadir:
This is the "ekem1" message pattern:
(kem_ciphertext, kem_shared_key) = ENCAPS(encap_key)
// EncryptAndHash(kem_ciphertext)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, kem_ciphertext, ad)
// MixHash(ciphertext)
h = SHA256(h || ciphertext)
// MixKey(kem_shared_key)
keydata = HKDF(chainKey, kem_shared_key, "", 64)
chainKey = keydata[0:31]
End of "ekem1" message pattern.
Alice KDF para Mensaje NSR
Después del patrón de mensaje ’ee’ y antes del patrón de mensaje ‘ss’, añadir:
This is the "ekem1" message pattern:
// DecryptAndHash(kem_ciphertext_section)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
kem_ciphertext = DECRYPT(k, n, kem_ciphertext_section, ad)
// MixHash(kem_ciphertext_section)
h = SHA256(h || kem_ciphertext_section)
// MixKey(kem_shared_key)
kem_shared_key = DECAPS(kem_ciphertext, decap_key)
keydata = HKDF(chainKey, kem_shared_key, "", 64)
chainKey = keydata[0:31]
End of "ekem1" message pattern.
KDF para split()
sin cambios
Formato de Mensaje
Formato NS
Cambios: El ratchet actual contenía la clave estática en la primera sección ChaCha, y la carga útil en la segunda sección. Con ML-KEM, ahora hay tres secciones. La primera sección contiene la clave pública PQ cifrada. La segunda sección contiene la clave estática. La tercera sección contiene la carga útil.
Formato cifrado:
+----+----+----+----+----+----+----+----+
| |
+ New Session Ephemeral +
| Public Key |
+ 32 bytes +
| Encoded with Elligator2 |
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM encap_key +
| ChaCha20 encrypted data |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for encap_key Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ X25519 Static Key +
| ChaCha20 encrypted data |
+ 32 bytes +
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Static Key Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| ChaCha20 encrypted data |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Payload Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
Formato descifrado:
Payload Part 1:
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM encap_key +
| |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
Payload Part 2:
+----+----+----+----+----+----+----+----+
| |
+ X25519 Static Key +
| (32 bytes) |
+ +
| |
+----+----+----+----+----+----+----+----+
Payload Part 3:
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
Tamaños:
| Type | Type Code | X len | NS len | NS Enc len | NS Dec len | PQ key len | pl len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 96+pl | 64+pl | pl | -- | pl |
| MLKEM512_X25519 | 5 | 32 | 912+pl | 880+pl | 800+pl | 800 | pl |
| MLKEM768_X25519 | 6 | 32 | 1296+pl | 1360+pl | 1184+pl | 1184 | pl |
| MLKEM1024_X25519 | 7 | 32 | 1680+pl | 1648+pl | 1568+pl | 1568 | pl |
Formato NSR
Cambios: El ratchet actual tiene una carga útil vacía para la primera sección ChaCha, y la carga útil en la segunda sección. Con ML-KEM, ahora hay tres secciones. La primera sección contiene el texto cifrado PQ encriptado. La segunda sección tiene una carga útil vacía. La tercera sección contiene la carga útil.
Formato cifrado:
+----+----+----+----+----+----+----+----+
| Session Tag 8 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Ephemeral Public Key +
| 32 bytes |
+ Encoded with Elligator2 +
| |
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM ciphertext +
| ChaCha20 encrypted data |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for ciphertext Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for key Section (no data) +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| ChaCha20 encrypted data |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Payload Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
Formato descifrado:
Payload Part 1:
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM ciphertext +
| |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
Payload Part 2:
empty
Payload Part 3:
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
Tamaños:
| Type | Type Code | Y len | NSR len | NSR Enc len | NSR Dec len | PQ CT len | opt len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 72+pl | 32+pl | pl | -- | pl |
| MLKEM512_X25519 | 5 | 32 | 856+pl | 816+pl | 768+pl | 768 | pl |
| MLKEM768_X25519 | 6 | 32 | 1176+pl | 1136+pl | 1088+pl | 1088 | pl |
| MLKEM1024_X25519 | 7 | 32 | 1656+pl | 1616+pl | 1568+pl | 1568 | pl |
Análisis de Sobrecarga
Intercambio de Claves
Aumento de tamaño (bytes):
| Type | Pubkey (NS) | Ciphertext (NSR) |
|---|---|---|
| MLKEM512_X25519 | +816 | +784 |
| MLKEM768_X25519 | +1200 | +1104 |
| MLKEM1024_X25519 | +1584 | +1584 |
Velocidades según reporta CLOUDFLARE :
| Type | Relative speed |
|---|---|
| X25519 DH/keygen | baseline |
| MLKEM512 | 2.25x faster |
| MLKEM768 | 1.5x faster |
| MLKEM1024 | 1x (same) |
| XK | 4x DH (keygen + 3 DH) |
| MLKEM512_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 4.9x DH = 22% slower |
| MLKEM768_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 5.3x DH = 32% slower |
| MLKEM1024_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 6x DH = 50% slower |
Las categorías de seguridad de NIST se resumen en NIST-PQ-END diapositiva 10. Criterios preliminares: Nuestra categoría mínima de seguridad NIST debería ser 2 para protocolos híbridos y 3 para solo-PQ.
| Category | As Secure As |
|---|---|
| 1 | AES128 |
| 2 | SHA256 |
| 3 | AES192 |
| 4 | SHA384 |
| 5 | AES256 |
Estos son todos protocolos híbridos. Probablemente necesitemos preferir MLKEM768; MLKEM512 no es lo suficientemente seguro.
Categorías de seguridad NIST FIPS203 :
| Algorithm | Security Category |
|---|---|
| MLKEM512 | 1 |
| MLKEM768 | 3 |
| MLKEM1024 | 5 |
El tipo recomendado para el soporte inicial, basado en la categoría de seguridad y la longitud de clave, es:
MLKEM768_X25519 (tipo 6)
Notas de Implementación
Soporte de Bibliotecas
Las librerías Bouncycastle, BoringSSL y WolfSSL ahora soportan MLKEM. El soporte de OpenSSL está en su versión 3.5 lanzada el 8 de abril de 2025 OPENSSL .
Tunnels Compartidos
La clasificación/detección automática de múltiples protocolos en los mismos túneles debería ser posible basándose en una verificación de longitud del mensaje 1 (Mensaje de Nueva Sesión). Usando MLKEM512_X25519 como ejemplo, la longitud del mensaje 1 es 816 bytes mayor que el protocolo ratchet actual, y el tamaño mínimo del mensaje 1 (con solo una carga útil DateTime incluida) es de 919 bytes. La mayoría de los tamaños del mensaje 1 con el ratchet actual tienen una carga útil de menos de 816 bytes, por lo que pueden clasificarse como ratchet no híbrido. Los mensajes grandes probablemente sean POSTs que son raros.
Por lo tanto, la estrategia recomendada es:
- Si el mensaje 1 es menor de 919 bytes, es el protocolo ratchet actual.
- Si el mensaje 1 es mayor o igual a 919 bytes, probablemente sea MLKEM512_X25519. Prueba MLKEM512_X25519 primero, y si falla, prueba el protocolo ratchet actual.
Esto debería permitirnos soportar eficientemente el ratchet estándar y el ratchet híbrido en el mismo destino, tal como anteriormente soportamos ElGamal y ratchet en el mismo destino. Por lo tanto, podemos migrar al protocolo híbrido MLKEM mucho más rápidamente que si no pudiéramos soportar protocolos duales para el mismo destino, porque podemos añadir soporte MLKEM a destinos existentes.
Las combinaciones soportadas requeridas son:
- X25519 + MLKEM512
- X25519 + MLKEM768
- X25519 + MLKEM1024
Las siguientes combinaciones pueden ser complejas y NO se requiere que sean compatibles, pero pueden serlo, dependiendo de la implementación:
- Más de un MLKEM
- ElG + uno o más MLKEM
- X25519 + uno o más MLKEM
- ElG + X25519 + uno o más MLKEM
No es necesario soportar múltiples algoritmos MLKEM (por ejemplo, MLKEM512_X25519 y MLKEM_768_X25519) en el mismo destino. Elige solo uno. Depende de la implementación.
No es necesario soportar tres algoritmos (por ejemplo X25519, MLKEM512_X25519, y MLKEM769_X25519) en el mismo destino. La clasificación y estrategia de reintentos puede ser demasiado compleja. La configuración y la interfaz de usuario de configuración pueden ser demasiado complejas. Dependiente de la implementación.
No es necesario soportar algoritmos ElGamal e híbridos en el mismo destino. ElGamal está obsoleto, y solo ElGamal + híbrido (sin X25519) no tiene mucho sentido. Además, tanto los Mensajes de Nueva Sesión ElGamal como Híbridos son grandes, por lo que las estrategias de clasificación a menudo tendrían que intentar ambos descifrados, lo cual sería ineficiente. Dependiente de la implementación.
Los clientes pueden usar las mismas claves estáticas X25519 o diferentes para los protocolos X25519 e híbrido en los mismos túneles, dependiendo de la implementación.
Secreto Perfecto Hacia Adelante
La especificación ECIES permite Garlic Messages en la carga útil del New Session Message, lo que permite la entrega 0-RTT del paquete de streaming inicial, generalmente un HTTP GET, junto con el leaseset del cliente. Sin embargo, la carga útil del New Session Message no tiene forward secrecy (secreto hacia adelante). Como esta propuesta enfatiza el forward secrecy mejorado para ratchet, las implementaciones pueden o deberían diferir la inclusión de la carga útil de streaming, o el mensaje completo de streaming, hasta el primer Existing Session Message. Esto sería a expensas de la entrega 0-RTT. Las estrategias también pueden depender del tipo de tráfico o tipo de tunnel, o en GET vs. POST, por ejemplo. Dependiente de la implementación.
Nuevo Tamaño de Sesión
MLKEM aumentará dramáticamente el tamaño del Mensaje de Nueva Sesión, como se describe anteriormente. Esto puede disminuir significativamente la confiabilidad de la entrega del Mensaje de Nueva Sesión a través de tunnels, donde deben ser fragmentados en múltiples mensajes de tunnel de 1024 bytes. El éxito de la entrega es proporcional al número exponencial de fragmentos. Las implementaciones pueden usar varias estrategias para limitar el tamaño del mensaje, a expensas de la entrega 0-RTT. Dependiente de la implementación.