Estado
Beta Q1 2026, lanzamiento Q2 2026
Descripción general
Esta es la variante híbrida post-cuántica del protocolo de transporte NTCP2, tal como se diseñó en la Propuesta 169. Consulte dicha propuesta para obtener información adicional de contexto.
PQ Hybrid NTCP2 solo se define en la misma dirección y puerto que el NTCP2 estándar. No está permitido operar en un puerto diferente ni sin soporte de NTCP2 estándar, y esto no cambiará durante varios años, hasta que el NTCP2 estándar sea obsoleto.
Esta especificación documenta únicamente los cambios necesarios en el NTCP2 estándar para soportar PQ Hybrid. Consulte la especificación de NTCP2 para los detalles de implementación de referencia.
Diseño
Admitimos los estándares NIST FIPS 203 y 204 FIPS 203 FIPS 204 , los cuales están basados en CRYSTALS-Kyber y CRYSTALS-Dilithium (versiones 3.1, 3 y anteriores), pero NO son compatibles con ellos.
Intercambio de claves
PQ KEM proporciona únicamente claves efímeras y no admite directamente handshakes de clave estática como Noise XK e IK. Los tipos de cifrado son los mismos que se utilizan en PQ Hybrid Ratchet y están definidos en el documento de estructuras comunes /docs/specs/common-structures/ ; al igual que en FIPS 203 , los tipos híbridos solo se definen en combinación con X25519.
Los tipos de cifrado son:
| Type | Code | NTCP2 Version |
|---|---|---|
| MLKEM512_X25519 | 5 | 3 |
| MLKEM768_X25519 | 6 | 4 |
| MLKEM1024_X25519 | 7 | 5 |
Los nuevos tipos de cifrado se indican en los RouterAddresses. El tipo de cifrado en el certificado de clave continuará siendo el tipo 4.
Especificación
Patrones de protocolo de enlace
Los handshakes (intercambios de autenticación) utilizan patrones de handshake del Protocolo Noise .
Se utiliza la siguiente correspondencia 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 la confidencialidad directa híbrida (hfs) son las especificadas en la sección 5 de Noise HFS spec :
XK: XKhfs:
<- s <- s
... ...
-> e, es, p -> e, es, e1, p
<- e, ee, p <- e, ee, ekem1, p
-> s, se -> s, se
<- 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, según lo especificado en la sección 4 de la especificación Noise HFS :
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, tal como se especifica en la sección 4 de Noise HFS spec :
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)
KDF del protocolo de handshake Noise
Descripción general
El handshake híbrido se define en la especificación Noise HFS . El primer mensaje, de Alice a Bob, contiene e1, la clave de encapsulación, antes del payload del mensaje. Esto se trata como una clave estática adicional; se llama a EncryptAndHash() (como Alice) o DecryptAndHash() (como Bob). Luego se procesa el payload del mensaje de forma habitual.
El segundo mensaje, de Bob a Alice, contiene ekem1, el texto cifrado, antes del payload del mensaje. Este se trata como una clave estática adicional; se llama a EncryptAndHash() sobre él (como Bob) o DecryptAndHash() (como Alice). Luego, se calcula el kem_shared_key y se llama a MixKey(kem_shared_key). A continuación, se procesa el payload del mensaje de la manera habitual.
Operaciones ML-KEM Definidas
Definimos las siguientes funciones correspondientes a los bloques de construcción criptográficos utilizados según lo definido en FIPS 203 .
(encap_key, decap_key) = PQ_KEYGEN()
Alice creates the encapsulation and decapsulation keys
The encapsulation key is sent in message 1.
encap_key and decap_key sizes vary based on ML-KEM variant.
(ciphertext, kem_shared_key) = ENCAPS(encap_key)
Bob calculates the ciphertext and shared key,
using the ciphertext received in message 1.
The ciphertext is sent in message 2.
ciphertext size varies based on ML-KEM variant.
The kem_shared_key is always 32 bytes.
kem_shared_key = DECAPS(ciphertext, decap_key)
Alice calculates the shared key,
using the ciphertext received in message 2.
The kem_shared_key is always 32 bytes.
Tenga en cuenta que tanto la encap_key como el texto cifrado están cifrados dentro de bloques ChaCha/Poly en los mensajes 1 y 2 del protocolo de enlace Noise. Serán descifrados como parte del proceso de protocolo de enlace.
El kem_shared_key se mezcla en la clave de encadenamiento con MixHash(). Consulte a continuación para obtener más detalles.
KDF de Alice para el Mensaje 1
Después del patrón de mensaje ’es’ y antes del payload, 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 el Mensaje 1
Después del patrón de mensaje ’es’ y antes del payload, 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 el Mensaje 2
Para XK: Después del patrón de mensaje ’ee’ y antes del payload, agregar:
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.
KDF de Alice para el Mensaje 2
Después del patrón de mensaje ’ee’, 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 el Mensaje 3 (solo XK)
sin cambios
KDF para split()
sin cambios
Detalles del protocolo de enlace
Identificadores de Noise
- “Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM512_ChaChaPoly_SHA256”
- “Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM768_ChaChaPoly_SHA256”
- “Noise_XKhfsaesobfse+hs2+hs3_25519+MLKEM1024_ChaChaPoly_SHA256”
1) SessionRequest
Cambios: El NTCP2 actual contiene solo las opciones en la sección ChaCha. Con ML-KEM, la sección ChaCha también contendrá la clave pública PQ cifrada.
Para que PQ y no-PQ NTCP2 puedan ser compatibles en la misma dirección y puerto del router, utilizamos el bit más significativo del valor X (clave pública efímera X25519) para indicar que se trata de una conexión PQ. Este bit siempre está sin establecer en las conexiones no-PQ.
Para Alice, después de que el mensaje sea cifrado por Noise, pero antes de la ofuscación AES de X, establezca X[31] |= 0x7f.
Para Bob, después de la des-ofuscación AES de X, comprobar X[31] & 0x80. Si el bit está establecido, borrarlo con X[31] &= 0x7f, y descifrar mediante Noise como una conexión PQ. Si el bit está vacío, descifrar mediante Noise como una conexión no PQ de la forma habitual.
Para PQ NTCP2 anunciado en una dirección de router y puerto diferente, esto no es necesario.
Para obtener información adicional, consulte la sección Direcciones publicadas a continuación.
Contenido sin procesar:
+----+----+----+----+----+----+----+----+
| MS bit set to 1 and then |
+ obfuscated with RH_B +
| AES-CBC-256 encrypted X |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaChaPoly frame (MLKEM) |
+ (see table below for length) +
| k defined in KDF for message 1 |
+ n = 0 +
| see KDF for associated data |
~ n = 0 ~
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaChaPoly frame (options) |
+ 32 bytes +
| k defined in KDF for message 1 |
+ n = 0 +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
~ padding (optional) ~
| length defined in options block |
+----+----+----+----+----+----+----+----+
Same as current specification except add a second ChaChaPoly frame
Datos sin cifrar (etiqueta de autenticación Poly1305 no mostrada):
+----+----+----+----+----+----+----+----+
| |
+ +
| X |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ML-KEM encap_key |
+ (see table below for length) +
| |
+----+----+----+----+----+----+----+----+
| options |
+ (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Nota: el campo de versión en el bloque de opciones del mensaje 1 debe establecerse en 2, incluso para conexiones PQ.
Tamaños:
| Type | Type Code | X len | Msg 1 len | Msg 1 Enc len | Msg 1 Dec len | PQ key len | opt len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 64+pad | 32 | 16 | -- | 16 |
| MLKEM512_X25519 | 5 | 32 | 880+pad | 848 | 816 | 800 | 16 |
| MLKEM768_X25519 | 6 | 32 | 1264+pad | 1232 | 1200 | 1184 | 16 |
| MLKEM1024_X25519 | 7 | 32 | 1648+pad | 1616 | 1584 | 1568 | 16 |
2) SessionCreated
Contenido sin procesar:
+----+----+----+----+----+----+----+----+
| |
+ obfuscated with RH_B +
| AES-CBC-256 encrypted Y |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaChaPoly frame (MLKEM) |
+ Encrypted and authenticated data +
- (see table below for length) -
+ k defined in KDF for message 2 +
| n = 0; see KDF for associated data |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaChaPoly frame (options) |
+ Encrypted and authenticated data +
- 32 bytes -
+ k defined in KDF for message 2 +
| n = 0; see KDF for associated data |
+ +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Same as current specification except add a second ChaChaPoly frame
Datos sin cifrar (etiqueta de autenticación Poly1305 no mostrada):
+----+----+----+----+----+----+----+----+
| |
+ +
| Y |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ML-KEM Ciphertext |
+ (see table below for length) +
| |
+----+----+----+----+----+----+----+----+
| options |
+ (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Tamaños:
| Type | Type Code | Y len | Msg 2 len | Msg 2 Enc len | Msg 2 Dec len | PQ CT len | opt len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 64+pad | 32 | 16 | -- | 16 |
| MLKEM512_X25519 | 5 | 32 | 848+pad | 816 | 784 | 768 | 16 |
| MLKEM768_X25519 | 6 | 32 | 1136+pad | 1104 | 1104 | 1088 | 16 |
| MLKEM1024_X25519 | 7 | 32 | 1616+pad | 1584 | 1584 | 1568 | 16 |
3) SessionConfirmed
Sin cambios
Función de Derivación de Claves (KDF) (para la fase de datos)
Sin cambios
Direcciones publicadas
En todos los casos, utilice el nombre del transporte NTCP2 como de costumbre.
Utiliza la misma dirección/puerto que la variante sin PQ y sin firewall. Solo se admite una variante PQ. En la dirección del router, publica v=2 (como de costumbre) y el nuevo parámetro pq=[3|4|5] para indicar MLKEM 512/768/1024. Alice establece el MSB de la clave efímera (key[31] & 0x80) en la solicitud de sesión para indicar que se trata de una conexión híbrida. Ver más arriba. Los routers más antiguos ignorarán el parámetro pq y se conectarán sin PQ como de costumbre.
No está soportado tener una dirección/puerto diferente como no-PQ, o solo-PQ sin firewall. Esto no se implementará hasta que NTCP2 no-PQ sea deshabilitado, dentro de varios años. Cuando no-PQ sea deshabilitado, podrán soportarse múltiples variantes PQ, pero solo una por dirección. Cuando sea soportado, en la dirección del router se publicará v=[3|4|5] para indicar MLKEM 512/768/1024. Alice no establece el MSB de la clave efímera. Los routers más antiguos verificarán el parámetro v y omitirán esta dirección como no soportada.
Direcciones con firewall (sin IP publicada): En la dirección del router, publicar v=2 (como de costumbre). No es necesario publicar un parámetro pq.
Alice puede conectarse a un Bob PQ utilizando la variante PQ que Bob publica, independientemente de si Alice anuncia soporte PQ en su router info, o si anuncia la misma variante.
Relleno máximo
En la especificación actual, los mensajes 1 y 2 están definidos para tener una cantidad “razonable” de relleno, con un rango recomendado de 0 a 31 bytes y sin un máximo especificado.
A través de la API 0.9.68 (versión 2.11.0), Java I2P implementó un máximo de 256 bytes de relleno para conexiones no-PQ, aunque esto no estaba documentado anteriormente. A partir de la API 0.9.69 (versión 2.12.0), Java I2P implementa el mismo relleno máximo para conexiones no-PQ que para MLKEM-512. Véase la tabla a continuación.
Utilice el tamaño de mensaje definido como el relleno máximo, es decir, el relleno máximo duplicará el tamaño del mensaje para las conexiones PQ, de la siguiente manera:
| Message Max Padding | non-PQ (thru 0.9.68) | non-PQ (as of 0.9.69) | MLKEM-512 | MLKEM-768 | MLKEM-1024 |
|---|---|---|---|---|---|
| Session Request | 256 | 880 | 880 | 1264 | 1648 |
| Session Created | 256 | 848 | 848 | 1136 | 1616 |
Intercambio de claves
Aumento de tamaño (bytes):
| Type | Pubkey (Msg 1) | Ciphertext (Msg 2) |
|---|---|---|
| MLKEM512_X25519 | +816 | +784 |
| MLKEM768_X25519 | +1200 | +1104 |
| MLKEM1024_X25519 | +1584 | +1584 |
Las categorías de seguridad NIST se resumen en la presentación NIST , diapositiva 10. Criterios preliminares: nuestra categoría de seguridad NIST mínima debería ser 2 para protocolos híbridos y 3 para protocolos exclusivamente poscuánticos (PQ-only).
| Category | As Secure As |
|---|---|
| 1 | AES128 |
| 2 | SHA256 |
| 3 | AES192 |
| 4 | SHA384 |
| 5 | AES256 |
Todos estos son protocolos híbridos. Las implementaciones deben preferir MLKEM768; MLKEM512 no es suficientemente seguro.
Categorías de seguridad NIST FIPS 203 :
| Algorithm | Security Category |
|---|---|
| MLKEM512 | 1 |
| MLKEM768 | 3 |
| MLKEM1024 | 5 |
Soporte de biblioteca
Las bibliotecas Bouncycastle, BoringSSL y WolfSSL ya admiten MLKEM y MLDSA. El soporte de OpenSSL estará disponible en su versión 3.5 del 8 de abril de 2025 OpenSSL .
Identificación del tráfico entrante
Establecemos el MSB (bit más significativo) de la clave efímera (key[31] & 0x80) en la solicitud de sesión para indicar que se trata de una conexión híbrida. Esto nos permite ejecutar tanto NTCP estándar como NTCP híbrido en el mismo puerto. Solo se admite una variante híbrida para conexiones entrantes, la cual se anuncia en la dirección del router. Por ejemplo, pq=3 o pq=4.
Ofuscación
Como Alice, para una conexión PQ, antes de la ofuscación, establece X[31] |= 0x80. Esto convierte X en una clave pública X25519 no válida. Después de la ofuscación, AES-CBC la aleatorizará. El MSB de X será aleatorio después de la ofuscación.
Como Bob, verificar si (X[31] & 0x80) != 0 después de la de-ofuscación. Si es así, se trata de una conexión PQ.
La versión mínima del router requerida para NTCP2-PQ está por determinarse.
Nota: Los códigos de tipo son únicamente para uso interno. Los routers permanecerán como tipo 4, y el soporte se indicará en las direcciones del router.
Compatibilidad del Router
Nombres de Transporte
En todos los casos, utilice el nombre de transporte NTCP2 de la manera habitual. Los routers más antiguos ignorarán el parámetro pq y se conectarán con el NTCP2 estándar como de costumbre.