Utilizarea MLDSA și MLKEM în limbajul Go
Prezentare generală
Context
Încă de acum mult timp, capacitatea de calcul rapid a calculatoarelor cuantice a fost recunoscută ca o amenințare la adresa sistemelor de criptare existente. Acest lucru se datorează faptului că algoritmi existenți precum RSA sau ECC ar putea fi descifrați datorită acestei capacități de calcul a calculatoarelor cuantice. Totuși, odată cu materializarea conceptului de calculator cuantic în urmă cu câțiva ani, au început să fie cercetate și dezvoltate alternative, iar NIST a avansat cu standardizarea PQC (criptografia rezistentă la atacuri cuantice).
MLDSA și MLKEM
În cele din urmă, în august 2024, NIST a adoptat MLKEM și MLDSA, bazate pe CRYSTALS-Kyber și CRYSTALS-Dilithium, ca standarde. Ambii algoritmi funcționează pe baza problemei MLWE (Module Learning with Errors). Această formă este denumită de noi criptografie bazată pe rețele.
Criptografia bazată pe rețele, după cum sugerează și numele, este un sistem de criptare bazat pe dificultatea problemelor matematice pe o rețea. Nu am cunoștințe matematice aprofundate în acest sens, dar într-o singură linie este rezumată ca fiind problema rezolvării unei ecuații liniare cu zgomot pe o rețea de module
. Nu-mi dau seama cât de dificilă este, dar se spune că această problemă este atât de dificilă încât nici măcar un calculator cuantic nu o poate rezolva.
MLDSA
Așadar, să aruncăm o privire mai întâi asupra MLDSA.
Structură
MLDSA, după cum se vede din nume, este un algoritm asimetric de semnare care trece în total prin următoarele 2 etape.
- Generarea semnăturii: Se generează o semnătură pentru un mesaj folosind o cheie privată.
- Verificarea semnăturii: Se verifică validitatea semnăturii generate folosind o cheie publică.
De asemenea, MLDSA are următoarele 3 caracteristici.
- strong existential unforgeability: Nu se poate genera o altă semnătură validă cu o singură semnătură și o cheie publică.
- chosen message attack: Nu se poate genera o nouă semnătură validă cu o cheie publică folosind o semnătură pentru orice mesaj.
- side-channel attack: Securitatea este ridicată prin utilizarea continuă a unei valori aleatoare noi și a unei valori pseudo-aleatoare derivate dintr-un mesaj în timpul semnării.
- domain separation: Se previn problemele de securitate repetitive prin utilizarea unor seed-uri diferite pentru parametri diferiți.
Cod
Acum, iată un exemplu simplu de cod în limbajul Go.
În acest exemplu, am folosit mldsa de la 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 // Se generează o cheie cu specificația 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 // Se generează o semnătură.
22 // Un lucru de reținut este că, începând cu versiunea din 22 decembrie 2024, apare o eroare dacă nu este crypto.Hash(0).
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 // Se verifică apelând schema cheii publice.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
Valoarea semnăturii este prea lungă și a fost omisă. Dacă doriți să vedeți întreaga valoare, rulați-o în playground.
Deși este codificată în base64, este puțin împovărător faptul că rezultă 3228 de octeți.
Mă simt puțin împovărat gândindu-mă că în curând va trebui să facem schimb de această dimensiune ca semnătură care să reziste calculatoarelor cuantice.
MLKEM
Structură
MLKEM este un mecanism de încapsulare a cheilor (Key Encapsulation Mechanism). KEM este un algoritm care permite generarea unei chei partajate între două părți folosind criptarea cu cheie publică. Mecanismul de schimb de chei MLKEM urmează următorul proces.
- Încapsularea cheii: Expeditorul generează un mesaj criptat (cipher text) și o cheie partajată (shared key) folosind cheia publică a destinatarului. Acest mesaj criptat este transmis inițial destinatarului pentru a fi folosit.
- Decapsularea cheii: Destinatarul extrage cheia partajată din mesajul criptat folosind propria cheie privată.
Există în total 3 parametri pentru MLKEM. Există MLKEM-512, MLKEM-768 și MLKEM-1024, iar cu cât este mai mic, cu atât cheile și textele criptate sunt mai mici, iar cu cât este mai mare, cu atât cheile și textele criptate sunt mai lungi, iar nivelul de securitate este mai ridicat.
Cod
MLKEM va fi adăugat în go 1.24, așa că am folosit go 1.24rc1, care este disponibil în acest moment.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Se generează cheia privată a destinatarului.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // În MLKEM, se folosește termenul EncapsulationKey în loc de PublicKey.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Am clonat-o pentru a arăta simplu că cheia poate fi extrasă cu Bytes() EncapsulationKey și refolosită cu NewEncapsulationKeyX.
20 // Desigur, în realitate, acest proces este procesul prin care expeditorul transformă într-un obiect cheia EncapsulationKey a destinatarului, care fusese publicată sub formă de text.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // Cu Encapsulate, expeditorul generează textul criptat și cheia partajată.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Am clonat-o intenționat pentru a arăta cum să stocăm și să extragem cheia privată a destinatarului.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // Destinatarul folosește cheia privată pentru a Decapsulate textul criptat și pentru a genera o altă cheie partajată.
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=
Ca rezultat, putem vedea că se generează chei partajate de aceeași dimensiune!
Acest cod poate fi verificat și în playground.
Concluzie
Specificațiile, nivelurile de securitate sau dimensiunile cheilor private, cheilor publice, semnăturilor sau textelor criptate ale fiecărui algoritm pot fi rezumate după cum urmează. Fiecare dintre ele se mândrește cu dimensiuni mari, demne de numele de PQC.
Algoritm | Nivelul de securitate NIST | Dimensiunea cheii private | Dimensiunea cheii publice | Dimensiunea semnăturii/textului criptat |
---|---|---|---|---|
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 |
Ne așteptăm ca, datorită acestor algoritmi, să putem folosi un internet suficient de sigur chiar și pe calculatoarele cuantice, dar se pare că nu se poate evita ca mai multe operațiuni să fie necesare din cauza dimensiunilor relativ mari ale cheilor și semnăturilor/textelor criptate.
Cu toate acestea, deoarece limbajul Go are implementate în mod eficient fiecare algoritm, ne așteptăm ca acestea să fie utilizate activ pentru a vă proteja securitatea în locurile potrivite!