Utilisation de MLDSA et MLKEM dans le langage Go
Aperçu
Contexte
Depuis un certain temps, la rapidité de calcul des ordinateurs quantiques est perçue comme une menace pour les systèmes de chiffrement existants. Les algorithmes traditionnels tels que RSA ou ECC pourraient potentiellement être déchiffrés en raison de la puissance de calcul des ordinateurs quantiques. Cependant, depuis quelques années, le concept d'ordinateur quantique est devenu plus tangible, et des alternatives ont commencé à être étudiées et développées. Le NIST a mené une normalisation PQC (cryptographie post-quantique).
MLDSA et MLKEM
Finalement, en août 2024, le NIST a adopté MLKEM et MLDSA, basés sur CRYSTALS-Kyber et CRYSTALS-Dilithium, comme normes. Les deux algorithmes sont basés sur le problème MLWE (Module Learning with Errors). Cette approche est appelée cryptographie basée sur les réseaux.
La cryptographie basée sur les réseaux, comme son nom l'indique, est un système de chiffrement basé sur la difficulté de problèmes mathématiques sur des réseaux. Je n'ai pas de connaissances mathématiques approfondies à ce sujet, mais en résumé, il s'agit du problème de résolution d'équations linéaires bruitées sur un réseau modulaire
. Je ne sais pas à quel point c'est difficile, mais ce problème serait si difficile qu'il ne pourrait pas être résolu même par un ordinateur quantique.
MLDSA
Examinons d'abord MLDSA.
Structure
MLDSA, comme son nom l'indique, est un algorithme de signature asymétrique qui comporte au total deux étapes :
- Génération de signature : création d'une signature pour un message à l'aide d'une clé privée.
- Vérification de signature : vérification de la validité de la signature générée à l'aide d'une clé publique.
MLDSA possède également les 4 caractéristiques suivantes :
- forte non-falsifiabilité existentielle : il est impossible de générer une autre signature valide à partir d'une signature et d'une clé publique.
- attaque par message choisi : il est impossible de générer une nouvelle signature valide avec la clé publique, même avec la signature d'un message quelconque.
- attaque par canal auxiliaire : la sécurité est élevée car des valeurs aléatoires et des valeurs pseudo-aléatoires dérivées du message sont constamment utilisées lors de la signature.
- séparation de domaine : évite les problèmes de sécurité répétitifs en utilisant des seeds différents pour différents paramètres.
Code
Voici un exemple simple de code en langage Go. Cet exemple utilise mldsa de cloudflare/circl.
1package main
2
3import (
4 "crypto"
5 "crypto/rand"
6 "encoding/base64"
7 "fmt"
8
9 "github.com/cloudflare/circl/sign/mldsa/mldsa44"
10)
11
12func main() {
13 // Génère une clé avec la spécification mldsa44.
14 pub, priv, err := mldsa44.GenerateKey(rand.Reader)
15 if err != nil {
16 panic(err)
17 }
18
19 message := []byte("Hello, World!")
20
21 // Génère une signature.
22 // Il faut noter que, selon la version du 22 décembre 2024, une erreur se produit si crypto.Hash(0) n'est pas utilisé.
23 signature, err := priv.Sign(rand.Reader, message, crypto.Hash(0))
24 if err != nil {
25 panic(err)
26 }
27
28 encodedSignature := base64.URLEncoding.EncodeToString(signature)
29 fmt.Println(len(encodedSignature), encodedSignature)
30
31 // Effectue la vérification en appelant le schéma de la clé publique.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
La valeur de la signature est trop longue et a été omise. Si vous souhaitez consulter le texte complet, veuillez l'exécuter dans le playground.
Bien qu'il soit encodé en base64, il est un peu lourd d'avoir 3228 octets. Je suis un peu inquiet à l'idée que nous devions échanger cette taille de signature en tant que signature contre les ordinateurs quantiques dans un futur proche.
MLKEM
Structure
MLKEM est un mécanisme d'encapsulation de clés (Key Encapsulation Mechanism). KEM est un algorithme qui permet de générer une clé partagée entre deux parties en utilisant un schéma de cryptage à clé publique. Le mécanisme d'échange de clés de MLKEM suit le processus suivant :
- Encapsulation de clé : L'expéditeur génère un message chiffré (cipher text) et une clé partagée en utilisant la clé publique du destinataire. Ce message chiffré est initialement transmis au destinataire pour qu'il puisse l'utiliser.
- Décapsulation de clé : Le destinataire extrait la clé partagée du message chiffré à l'aide de sa clé privée.
Il existe trois paramètres pour MLKEM. MLKEM-512, MLKEM-768 et MLKEM-1024, plus ils sont petits, plus la clé et le texte chiffré sont petits, plus ils sont grands, plus la clé et le texte chiffré sont longs, et plus le niveau de sécurité est élevé.
Code
MLKEM sera ajouté dans go 1.24, nous avons donc utilisé go 1.24rc1 qui est disponible pour le moment.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Génère la PrivateKey du destinataire.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // MLKEM utilise le terme EncapsulationKey au lieu de PublicKey.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Pour montrer simplement que la clé peut être extraite avec Bytes() et réutilisée avec NewEncapsulationKeyX, nous l'avons clonée.
20 // Bien sûr, dans la pratique, ce processus est la façon dont l'expéditeur crée un objet à partir de la EncapsulationKey du destinataire qui a été divulguée sous forme de texte.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // L'expéditeur génère du texte chiffré et une clé partagée avec Encapsulate.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Nous l'avons délibérément cloné pour montrer comment enregistrer et récupérer la clé privée du destinataire.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // Le destinataire utilise la clé privée pour Decapsulate le texte chiffré et générer une autre clé partagée.
36 sharedKeyReceiver, err := clonedReceiverKey.Decapsulate(cipherText)
37 if err != nil {
38 panic(err)
39 }
40
41 fmt.Println(base64.StdEncoding.EncodeToString(SenderSharedKey))
42 fmt.Println(base64.StdEncoding.EncodeToString(sharedKeyReceiver))
43}
1Q1ciS818WFHTK7D4MTvsQvciMTGF+dSGqMllOxW80ew=
2Q1ciS818WFHTK7D4MTvsQvciMTGF+dSGqMllOxW80ew=
Nous pouvons voir que la même clé partagée est générée !
Ce code peut également être trouvé dans le playground.
Conclusion
Les spécifications, les niveaux de sécurité et les tailles des clés privées, des clés publiques, des signatures ou des textes chiffrés pour chaque algorithme peuvent être résumés comme suit. Ils affichent tous des tailles importantes, ce qui n'est pas indigne du nom de PQC.
Algorithme | Niveau de sécurité NIST | Taille de la clé privée | Taille de la clé publique | Taille de la signature/du texte chiffré |
---|---|---|---|---|
ML-DSA-44 | 2 | 2 560 | 1 312 | 2 420 |
ML-DSA-65 | 3 | 4 032 | 1 952 | 3 309 |
ML-DSA-87 | 5 | 4 896 | 2 592 | 4 627 |
ML-KEM-512 | 1 | 1 632 | 800 | 768 |
ML-KEM-768 | 3 | 2 400 | 1 184 | 1 088 |
ML-KEM-1024 | 5 | 3 168 | 1 568 | 1 568 |
Nous espérons que ces algorithmes nous permettront d'utiliser un Internet sûr même sur des ordinateurs quantiques, mais il semble inévitable qu'il y aura plus de calculs en raison de la taille relativement importante des clés et des signatures/textes chiffrés.
Néanmoins, comme chaque algorithme est efficacement implémenté en langage Go, nous espérons qu'il sera activement utilisé pour protéger votre sécurité au bon endroit !