Note
Implémentation, tests et déploiement en cours dans les différentes implémentations de router. Consultez la documentation de ces implémentations pour connaître le statut.
Aperçu
Il s’agit de la variante PQ Hybrid du protocole ECIES-X25519-AEAD-Ratchet ECIES . C’est la première phase de la proposition PQ globale Prop169 à être approuvée. Consultez cette proposition pour les objectifs généraux, les modèles de menaces, l’analyse, les alternatives et les informations supplémentaires.
Cette spécification ne contient que les différences par rapport au standard ECIES et doit être lue conjointement avec cette spécification.
Conception
Nous utilisons la norme NIST FIPS 203 FIPS203 qui est basée sur CRYSTALS-Kyber (versions 3.1, 3 et antérieures), mais n’est pas compatible avec celui-ci.
Les handshakes hybrides sont tels que spécifiés dans Noise-Hybrid .
Échange de clés
Nous définissons un échange de clés hybride pour Ratchet. PQ KEM fournit uniquement des clés éphémères et ne prend pas directement en charge les poignées de main à clé statique telles que Noise IK.
Nous définissons les trois variantes ML-KEM comme dans FIPS203 , pour 3 nouveaux types de chiffrement au total. Les types hybrides ne sont définis qu’en combinaison avec X25519.
Les nouveaux types de chiffrement sont :
| Type | Code |
|---|---|
| MLKEM512_X25519 | 5 |
| MLKEM768_X25519 | 6 |
| MLKEM1024_X25519 | 7 |
Nouvelle cryptographie requise
- ML-KEM (anciennement CRYSTALS-Kyber) FIPS203
- SHA3-128 (anciennement Keccak-256) FIPS202 Utilisé uniquement pour SHAKE128
- SHA3-256 (anciennement Keccak-512) FIPS202
- SHAKE128 et SHAKE256 (extensions XOF pour SHA3-128 et SHA3-256) FIPS202
Les vecteurs de test pour SHA3-256, SHAKE128, et SHAKE256 sont disponibles sur NIST-VECTORS .
Notez que la bibliothèque Java bouncycastle prend en charge tout ce qui précède. La prise en charge de la bibliothèque C++ se trouve dans OpenSSL 3.5 OPENSSL .
Spécification
Structures Communes
Voir la spécification des structures communes COMMON pour les longueurs de clés et les identifiants.
Modèles de négociation
Les handshakes utilisent les modèles de handshake 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é PQ éphémère à usage unique, envoyée d’Alice à Bob
- ekem1 = le texte chiffré KEM, envoyé de Bob à Alice
Les modifications suivantes à XK et IK pour la confidentialité persistante hybride (hfs) sont spécifiées dans Noise-Hybrid section 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)
Le motif e1 est défini comme suit, tel que spécifié dans Noise-Hybrid section 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)
Le motif ekem1 est défini comme suit, tel que spécifié dans la section 4 de Noise-Hybrid :
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)
Opérations ML-KEM définies
Nous définissons les fonctions suivantes correspondant aux blocs de construction cryptographiques utilisés tels que définis dans FIPS203 .
(encap_key, decap_key) = PQ_KEYGEN()
Alice crée les clés d’encapsulation et de décapsulation. La clé d’encapsulation est envoyée dans le message NS. Les tailles d’encap_key et decap_key varient selon la variante ML-KEM.
(ciphertext, kem_shared_key) = ENCAPS(encap_key)
Bob calcule le texte chiffré et la clé partagée, en utilisant le texte chiffré reçu dans le message NS. Le texte chiffré est envoyé dans le message NSR. La taille du texte chiffré varie selon la variante ML-KEM. La kem_shared_key fait toujours 32 octets.
kem_shared_key = DECAPS(ciphertext, decap_key)
Alice calcule la clé partagée, en utilisant le texte chiffré reçu dans le message NSR. La kem_shared_key fait toujours 32 octets.
Notez que l’encap_key et le texte chiffré sont tous deux chiffrés à l’intérieur des blocs ChaCha/Poly dans les messages 1 et 2 de la négociation Noise. Ils seront déchiffrés dans le cadre du processus de négociation.
La kem_shared_key est mélangée dans la clé de chaînage avec MixHash(). Voir ci-dessous pour les détails.
KDF de handshake Noise
Aperçu
Le handshake hybride est défini dans Noise-Hybrid . Le premier message, d’Alice à Bob, contient e1, la clé d’encapsulation, avant la charge utile du message. Ceci est traité comme une clé statique supplémentaire ; appelez EncryptAndHash() dessus (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. Ceci est traité comme une clé statique supplémentaire ; appelez EncryptAndHash() dessus (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.
Identifiants Noise
Voici les chaînes d’initialisation Noise :
- “Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256”
Alice KDF pour message NS
Après le modèle de message ’es’ et avant le modèle de message ’s’, 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).
KDF de Bob pour le message NS
Après le motif de message ’es’ et avant le motif de message ’s’, 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 Message NSR
Après le motif de message ’ee’ et avant le motif de message ‘se’, ajoutez :
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 d’Alice pour le message NSR
Après le modèle de message ’ee’ et avant le modèle de message ‘ss’, ajouter :
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 pour split()
inchangé
Format du Message
Format NS
Changements : Le ratchet actuel contenait la clé statique dans la première section ChaCha, et la charge utile dans la seconde section. Avec ML-KEM, il y a maintenant trois sections. La première section contient la clé publique PQ chiffrée. La deuxième section contient la clé statique. La troisième section contient la charge utile.
Format chiffré :
+----+----+----+----+----+----+----+----+
| |
+ 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 |
+----+----+----+----+----+----+----+----+
Format décrypté :
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 +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
Tailles :
| 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 |
Format NSR
Changements : Le ratchet actuel a une charge utile vide pour la première section ChaCha, et la charge utile dans la seconde section. Avec ML-KEM, il y a maintenant trois sections. La première section contient le texte chiffré PQ chiffré. La seconde section a une charge utile vide. La troisième section contient la charge utile.
Format chiffré :
+----+----+----+----+----+----+----+----+
| 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 |
+----+----+----+----+----+----+----+----+
Format décrypté :
Payload Part 1:
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM ciphertext +
| |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
Payload Part 2:
empty
Payload Part 3:
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
Tailles :
| 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 |
Analyse des frais généraux
Échange de clés
Augmentation de taille (octets) :
| Type | Pubkey (NS) | Ciphertext (NSR) |
|---|---|---|
| MLKEM512_X25519 | +816 | +784 |
| MLKEM768_X25519 | +1200 | +1104 |
| MLKEM1024_X25519 | +1584 | +1584 |
Vitesses rapportées par 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 |
Les catégories de sécurité NIST sont résumées dans NIST-PQ-END diapositive 10. Critères préliminaires : Notre catégorie de sécurité NIST minimale devrait être 2 pour les protocoles hybrides et 3 pour PQ uniquement.
| Category | As Secure As |
|---|---|
| 1 | AES128 |
| 2 | SHA256 |
| 3 | AES192 |
| 4 | SHA384 |
| 5 | AES256 |
Ce sont tous des protocoles hybrides. Il faut probablement privilégier MLKEM768 ; MLKEM512 n’est pas suffisamment sécurisé.
Catégories de sécurité NIST FIPS203 :
| Algorithm | Security Category |
|---|---|
| MLKEM512 | 1 |
| MLKEM768 | 3 |
| MLKEM1024 | 5 |
Le type recommandé pour le support initial, basé sur la catégorie de sécurité et la longueur de clé, est :
MLKEM768_X25519 (type 6)
Notes d’implémentation
Support de Bibliothèque
Les bibliothèques Bouncycastle, BoringSSL et WolfSSL prennent désormais en charge MLKEM. Le support d’OpenSSL est prévu dans leur version 3.5 du 8 avril 2025 OPENSSL .
Tunnels Partagés
La classification/détection automatique de plusieurs protocoles sur les mêmes tunnels devrait être possible en se basant sur une vérification de la longueur du message 1 (New Session Message). En utilisant MLKEM512_X25519 comme exemple, la longueur du message 1 est de 816 octets plus grande que le protocole ratchet actuel, et la taille minimale du message 1 (avec seulement une charge utile DateTime incluse) est de 919 octets. La plupart des tailles de message 1 avec le ratchet actuel ont une charge utile de moins de 816 octets, elles peuvent donc être classifiées comme ratchet non-hybride. Les messages volumineux sont probablement des POST qui sont rares.
La stratégie recommandée est donc :
- Si le message 1 fait moins de 919 octets, c’est le protocole ratchet actuel.
- Si le message 1 fait 919 octets ou plus, c’est probablement MLKEM512_X25519. Essayez d’abord MLKEM512_X25519, et si cela échoue, essayez le protocole ratchet actuel.
Cela devrait nous permettre de prendre en charge efficacement le ratchet standard et le ratchet hybride sur la même destination, tout comme nous avons précédemment pris en charge ElGamal et ratchet sur la même destination. Par conséquent, nous pouvons migrer vers le protocole hybride MLKEM beaucoup plus rapidement que si nous ne pouvions pas prendre en charge des protocoles doubles pour la même destination, car nous pouvons ajouter la prise en charge de MLKEM aux destinations existantes.
Les combinaisons prises en charge requises sont :
- X25519 + MLKEM512
- X25519 + MLKEM768
- X25519 + MLKEM1024
Les combinaisons suivantes peuvent être complexes et ne sont PAS obligatoirement supportées, mais peuvent l’être, selon l’implémentation :
- Plus d’un MLKEM
- ElG + un ou plusieurs MLKEM
- X25519 + un ou plusieurs MLKEM
- ElG + X25519 + un ou plusieurs MLKEM
Il n’est pas nécessaire de prendre en charge plusieurs algorithmes MLKEM (par exemple, MLKEM512_X25519 et MLKEM_768_X25519) sur la même destination. Choisissez-en un seul. Dépendant de l’implémentation.
Il n’est pas obligatoire de prendre en charge trois algorithmes (par exemple X25519, MLKEM512_X25519, et MLKEM769_X25519) sur la même destination. La classification et la stratégie de nouvelle tentative peuvent être trop complexes. La configuration et l’interface utilisateur de configuration peuvent être trop complexes. Dépendant de l’implémentation.
Il n’est pas requis de prendre en charge les algorithmes ElGamal et hybrides sur la même destination. ElGamal est obsolète, et ElGamal + hybride uniquement (pas de X25519) n’a pas beaucoup de sens. De plus, les messages de nouvelle session ElGamal et hybrides sont tous deux volumineux, donc les stratégies de classification devraient souvent essayer les deux déchiffrements, ce qui serait inefficace. Dépendant de l’implémentation.
Les clients peuvent utiliser les mêmes clés statiques X25519 ou des clés différentes pour les protocoles X25519 et hybride sur les mêmes tunnels, selon l’implémentation.
Confidentialité persistante
La spécification ECIES permet les Garlic Messages dans la charge utile du New Session Message, ce qui permet la livraison 0-RTT du paquet de streaming initial, généralement un HTTP GET, conjointement avec le leaseset du client. Cependant, la charge utile du New Session Message n’a pas de confidentialité persistante. Étant donné que cette proposition met l’accent sur une confidentialité persistante renforcée pour le ratchet, les implémentations peuvent ou devraient différer l’inclusion de la charge utile de streaming, ou du message de streaming complet, jusqu’au premier Existing Session Message. Cela se ferait au détriment de la livraison 0-RTT. Les stratégies peuvent également dépendre du type de trafic ou du type de tunnel, ou de GET vs. POST, par exemple. Dépendant de l’implémentation.
Taille de nouvelle session
MLKEM augmentera considérablement la taille du New Session Message, comme décrit ci-dessus. Cela peut diminuer significativement la fiabilité de la livraison du New Session Message à travers les tunnels, où ils doivent être fragmentés en plusieurs messages tunnel de 1024 octets. Le succès de livraison est proportionnel au nombre exponentiel de fragments. Les implémentations peuvent utiliser diverses stratégies pour limiter la taille du message, au détriment de la livraison 0-RTT. Dépendant de l’implémentation.