Statut
Bêta T1 2026, sortie T2 2026
Aperçu
Il s’agit de la variante hybride post-quantique du protocole de transport NTCP2, conçue dans la Proposition 169. Voir cette proposition pour des informations complémentaires.
Le NTCP2 hybride PQ est uniquement défini sur la même adresse et le même port que le NTCP2 standard. Le fonctionnement sur un port différent, ou sans prise en charge du NTCP2 standard, n’est pas autorisé, et ne le sera pas pendant plusieurs années, jusqu’à ce que le NTCP2 standard soit obsolète.
Cette spécification décrit uniquement les modifications nécessaires au NTCP2 standard pour prendre en charge le hybride PQ. Voir la spécification NTCP2 pour les détails de mise en œuvre de base.
Conception
Nous prenons en charge les normes NIST FIPS 203 et 204 FIPS 203 FIPS 204 , qui sont basées sur CRYSTALS-Kyber et CRYSTALS-Dilithium (versions 3.1, 3 et antérieures), mais ne sont PAS compatibles avec celles-ci.
Échange de clés
PQ KEM fournit uniquement des clés éphémères et ne prend pas en charge directement les échanges de clés statiques comme Noise XK et IK. Les types de chiffrement sont les mêmes que ceux utilisés dans le système hybride PQ Ratchet et sont définis dans le document sur les structures communes /docs/specs/common-structures/ , comme dans FIPS 203 . Les types hybrides ne sont définis qu’en combinaison avec X25519.
Les types de chiffrement sont :
| Type | Code | NTCP2 Version |
|---|---|---|
| MLKEM512_X25519 | 5 | 3 |
| MLKEM768_X25519 | 6 | 4 |
| MLKEM1024_X25519 | 7 | 5 |
Les nouveaux types de chiffrement sont indiqués dans les RouterAddresses. Le type de chiffrement dans le certificat clé restera de type 4.
Spécification
Motifs de poignée de main
Les poignées de main utilisent les schémas de poignée de main du protocole Noise .
La correspondance de lettres suivante est utilisée :
- e = clé éphémère à usage unique
- s = clé statique
- p = charge utile du message
- e1 = clé éphémère PQ à usage unique, envoyée d’Alice à Bob
- ekem1 = le chiffré KEM, envoyé de Bob à Alice
Les modifications suivantes apportées à XK et IK pour la confidentialité post-compromise hybride (hfs) sont spécifiées dans la section 5 de la spécification Noise HFS :
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)
Le motif e1 est défini comme suit, tel que spécifié dans la section 4 de la spécification 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)
Le motif ekem1 est défini comme suit, tel que spécifié dans la section 4 de la spécification Noise HFS :
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 de poignée de main Noise
Aperçu
Le handshake hybride est défini dans la spécification Noise HFS . Le premier message, d’Alice vers Bob, contient e1, la clé d’encapsulation, avant la charge utile du message. Celle-ci est traitée comme une clé statique supplémentaire ; appliquez-lui EncryptAndHash() (en tant qu’Alice) ou DecryptAndHash() (en tant que Bob). Ensuite, traitez la charge utile du message comme d’habitude.
Le deuxième message, de Bob à Alice, contient ekem1, le texte chiffré, avant la charge utile du message. Cela est traité comme une clé statique supplémentaire ; appelez EncryptAndHash() avec celle-ci (en tant que Bob) ou DecryptAndHash() (en tant qu’Alice). Ensuite, calculez la kem_shared_key et appelez MixKey(kem_shared_key). Puis, traitez la charge utile du message comme d’habitude.
Opérations ML-KEM définies
Nous définissons les fonctions suivantes correspondant aux composants cryptographiques utilisés, tels que définis dans 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.
(chiffre, clé_partagée_kem) = ENCAPS(clé_encap)
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.
Notez que la clé de encapsulation (encap_key) et le texte chiffré sont encryptés à l’intérieur de blocs ChaCha/Poly dans les messages 1 et 2 de la poignée de main Noise. Ils seront déchiffrés dans le cadre du processus de poignée de main.
La clé partagée kem_shared_key est combinée à la clé de chaînage à l’aide de MixHash(). Voir ci-dessous pour plus de détails.
Alice KDF pour le message 1
Après le motif de message ’es’ et avant la charge utile, ajoutez :
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).
Bob KDF pour le message 1
Après le motif de message ’es’ et avant la charge utile, ajoutez :
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).
Bob KDF pour le message 2
Pour XK : Après le motif de message ’ee’ et avant la charge utile, ajouter :
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.
// AEAD parameters for payload section
... as in standard SSU2 ...
k = keydata[32:63]
...
Alice KDF pour le message 2
Après le motif de message ’ee’, ajoutez :
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.
// AEAD parameters for payload section
... as in standard SSU2 ...
k = keydata[32:63]
...
KDF pour le message 3 (XK uniquement)
unchanged
KDF pour split()
unchanged
Détails de la poignée de main
Identifiants de bruit
- “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
Changements : le NTCP2 actuel contient uniquement les options dans une seule section ChaCha. Avec ML-KEM, une nouvelle section ChaCha apparaîtra avant les options, contenant la clé publique PQ chiffrée.
Afin que les connexions NTCP2 PQ et non-PQ puissent être prises en charge sur la même adresse et le même port du routeur, nous utilisons le bit de poids fort de la valeur X (clé publique éphémère X25519) pour indiquer qu’il s’agit d’une connexion PQ. Ce bit est toujours à zéro pour les connexions non-PQ.
Pour Alice, après le chiffrement du message par Noise, mais avant l’obfuscation AES de X, définir X[31] |= 0x7f.
Pour Bob, après la désobfuscation AES de X, tester X[31] & 0x80. Si le bit est positionné, le réinitialiser avec X[31] &= 0x7f, puis déchiffrer via Noise comme une connexion PQ. Si le bit n’est pas positionné, déchiffrer via Noise comme une connexion non-PQ, comme d’habitude.
Pour le PQ NTCP2 annoncé sur une adresse et un port de routeur différents, cela n’est pas requis.
Pour plus d’informations, voir la section Adresses publiées ci-dessous.
Contenu brut :
+----+----+----+----+----+----+----+----+
| MS bit set to 1 and then |
+ obfuscated with RH_B +
| AES-CBC-256 encrypted X |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaChaPoly encrypted data (MLKEM) |
+ (see table below for length) +
| k defined in KDF for message 1 |
+ n = 0 +
| see KDF for associated data |
~ ~
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data (options) |
+ 16 bytes +
| k defined in KDF for message 1 |
+ n = 1 +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
~ padding (optional) ~
| length defined in options block |
+----+----+----+----+----+----+----+----+
Same as current specification except add a second ChaChaPoly frame
Données non chiffrées (balise d’authentification Poly1305 non affichée) :
+----+----+----+----+----+----+----+----+
| |
+ +
| X |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ML-KEM encap_key |
+ (see table below for length) +
| |
+----+----+----+----+----+----+----+----+
| options |
+ (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Remarque : le champ de version dans le bloc d’options du message 1 doit être défini à 2, même pour les connexions PQ.
Tailles :
| 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
Modifications : le NTCP2 actuel contient uniquement les options dans une seule section ChaCha. Avec ML-KEM, une nouvelle section ChaCha apparaîtra avant les options, contenant le chiffré PQC encrypté.
Contenu brut :
+----+----+----+----+----+----+----+----+
| |
+ obfuscated with RH_B +
| AES-CBC-256 encrypted Y |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaCha20 encrypted data (MLKEM) |
- (see table below for length) -
+ k defined in KDF for message 2 +
| (before mixKey) |
+ n = 0; see KDF for associated data +
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| ChaCha20 encrypted data (options) |
- 16 bytes -
+ k defined in KDF for message 2 +
| (after mixKey) |
+ n = 0; see KDF for associated data +
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Same as current specification except add a second ChaChaPoly frame
Données non chiffrées (balise d’authentification Poly1305 non affichée) :
+----+----+----+----+----+----+----+----+
| |
+ +
| Y |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| ML-KEM Ciphertext |
+ (see table below for length) +
| |
+----+----+----+----+----+----+----+----+
| options |
+ (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| unencrypted authenticated |
+ padding (optional) +
| length defined in options block |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Tailles :
| 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
Inchangé
Fonction de dérivation de clé (KDF) (pour la phase de données)
Inchangé
Adresses publiées
Dans tous les cas, utilisez le nom de transport NTCP2 comme d’habitude.
Utilisez la même adresse/port que pour les connexions non-PQ et non-protégées par pare-feu. Un seul variant PQ est pris en charge. Dans l’adresse du routeur, publiez v=2 (comme d’habitude) et le nouveau paramètre pq=[3|4|5] pour indiquer MLKEM 512/768/1024. Alice définit le bit de poids fort de la clé éphémère (key[31] & 0x80) dans la demande de session pour indiquer qu’il s’agit d’une connexion hybride. Voir ci-dessus. Les routeurs plus anciens ignoreront le paramètre pq et se connecteront en non-PQ comme d’habitude.
L’utilisation d’une adresse/port différente pour les connexions non-PQ ou uniquement PQ, non protégées par pare-feu, n’est PAS prise en charge. Cela ne sera pas implémenté tant que NTCP2 non-PQ ne sera pas désactivé, ce qui n’aura lieu que dans plusieurs années. Lorsque le mode non-PQ sera désactivé, plusieurs variantes PQ pourront être prises en charge, mais une seule par adresse. Quand cela sera pris en charge, le routeur devra publier v=[3|4|5] dans son adresse pour indiquer MLKEM 512/768/1024. Alice ne définit pas le bit de poids fort (MSB) de la clé éphémère. Les anciens routeurs vérifieront le paramètre v et ignoreront cette adresse en la considérant comme non prise en charge.
Adresses derrière pare-feu (aucune IP publiée) : Dans l’adresse du routeur, publiez v=2 (comme d’habitude). Il n’est pas nécessaire de publier un paramètre pq.
Alice peut se connecter à un Bob PQ en utilisant la variante PQ que Bob publie, qu’Alice annonce ou non le support de pq dans ses informations de routeur, ou qu’elle annonce la même variante.
Remplissage maximal
Dans la spécification actuelle, les messages 1 et 2 sont définis comme devant comporter une quantité « raisonnable » de bourrage, une plage de 0 à 31 octets étant recommandée, sans maximum spécifié.
Jusqu’à l’API 0.9.68 (version 2.11.0), Java I2P appliquait un remplissage maximal de 256 octets pour les connexions non-PQ, mais cela n’avait pas été documenté auparavant. À compter de l’API 0.9.69 (version 2.12.0), Java I2P applique le même remplissage maximal pour les connexions non-PQ que pour MLKEM-512. Voir le tableau ci-dessous.
Utiliser la taille de message définie comme remplissage maximal, c’est-à-dire que le remplissage maximal doublera la taille du message pour les connexions PQ, comme suit :
| 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 |
Échange de clés
Augmentation de la taille (octets) :
| Type | Pubkey (Msg 1) | Ciphertext (Msg 2) |
|---|---|---|
| MLKEM512_X25519 | +816 | +784 |
| MLKEM768_X25519 | +1200 | +1104 |
| MLKEM1024_X25519 | +1584 | +1584 |
Les catégories de sécurité du NIST sont résumées dans la diapositive 10 de la présentation NIST . Critères préliminaires : Notre catégorie de sécurité NIST minimale devrait être 2 pour les protocoles hybrides et 3 pour les systèmes uniquement PQC (PQ-only).
| Category | As Secure As |
|---|---|
| 1 | AES128 |
| 2 | SHA256 |
| 3 | AES192 |
| 4 | SHA384 |
| 5 | AES256 |
Ce sont tous des protocoles hybrides. Les implémentations devraient privilégier MLKEM768 ; MLKEM512 n’est pas suffisamment sécurisé.
Catégories de sécurité NIST FIPS 203 :
| Algorithm | Security Category |
|---|---|
| MLKEM512 | 1 |
| MLKEM768 | 3 |
| MLKEM1024 | 5 |
Support de la bibliothèque
Les bibliothèques Bouncycastle, BoringSSL et WolfSSL prennent désormais en charge MLKEM et MLDSA. Le support par OpenSSL sera inclus dans leur version 3.5 prévue pour le 8 avril 2025 OpenSSL .
Identification du trafic entrant
Nous définissons le bit de poids fort (MSB) de la clé éphémère (key[31] & 0x80) dans la demande de session pour indiquer qu’il s’agit d’une connexion hybride. Cela nous permet d’exécuter à la fois le NTCP standard et le NTCP hybride sur le même port. Un seul variant hybride est pris en charge pour les connexions entrantes, et est annoncé dans l’adresse du routeur. Par exemple, pq=3 ou pq=4.
Obfuscation
En tant qu’Alice, pour une connexion PQ, avant l’obfuscation, définir X[31] |= 0x80. Cela rend X une clé publique X25519 invalide. Après l’obfuscation, AES-CBC la rendra aléatoire. Le bit de poids fort (MSB) de X sera aléatoire après l’obfuscation.
En tant que Bob, tester si (X[31] & 0x80) != 0 après désobfuscation. Si c’est le cas, il s’agit d’une connexion PQ.
La version minimale du routeur requise pour NTCP2-PQ est à déterminer.
Remarque : les codes de type sont destinés uniquement à un usage interne. Les routeurs resteront de type 4, et la prise en charge sera indiquée dans les adresses des routeurs.
Compatibilité du routeur
Noms des transports
Dans tous les cas, utilisez le nom de transport NTCP2 comme d’habitude. Les routeurs plus anciens ignoreront le paramètre pq et se connecteront avec NTCP2 standard comme d’habitude.