GoSuda

Experiență cu utilizarea MLDSA și MLKEM în limbajul Go

By snowmerak views ...

Prezentare generală

Context

De ceva vreme, rapiditatea calculului cuantic a fost percepută ca o amenințare pentru sistemele criptografice existente. Motivul este că RSA și ECC, de exemplu, ar putea fi sparte prin această capacitate de calcul a computerelor cuantice. Totuși, din câțiva ani, conceptul de computer cuantic a început să devină tangibil, iar alternativele la acestea au început să fie cercetate și dezvoltate, NIST desfășurând un proces de standardizare pentru PQC (criptografie post-cuantică).

MLDSA și MLKEM

În cele din urmă, în august 2024, NIST a adoptat ca standarde MLKEM și MLDSA, bazate pe CRYSTALS-Kyber și CRYSTALS-Dilithium. Cei doi algoritmi funcționează pe baza problemei MLWE (Module Learning with Errors). Acest tip de criptografie este denumit de noi criptografie bazată pe rețele (lattice-based cryptography).

Criptografia bazată pe rețele este, după cum sugerează și numele, un sistem criptografic bazat pe dificultatea problemelor matematice din rețele. Nu dețin cunoștințe matematice profunde în acest domeniu, însă, rezumat într-o singură propoziție, aceasta constă în problema rezolvării unui sistem de ecuații liniare cu zgomot într-o rețea modulară. Cât de dificilă este această problemă nu îmi este clar, dar se spune că ea este atât de grea încât nu poate fi rezolvată nici de computerele cuantice.

MLDSA

Să începem, deci, prin a afla despre MLDSA.

Structură

După cum sugerează numele, MLDSA este un algoritm de semnătură asimetrică, care parcurge în total următoarele două etape:

  1. Generarea semnăturii: generarea unei semnături pentru un mesaj folosind cheia privată
  2. Verificarea semnăturii: confirmarea validității semnăturii generate folosind cheia publică

De asemenea, MLDSA are următoarele 3 caracteristici:

  1. Strong existential unforgeability: pe baza unei semnături și a cheii publice nu se poate genera o altă semnătură validă.
  2. Chosen message attack: nici cu semnătura unui mesaj oarecare nu se poate genera, folosind cheia publică, o nouă semnătură validă.
  3. Side-channel attack: la semnare se utilizează continuu valori aleatoare noi și valori pseudo-aleatoare derivate din mesaj, ceea ce ridică nivelul de securitate.
  4. Domain separation: pentru parametri diferiți se utilizează seed-uri diferite, prevenind astfel probleme de securitate repetitive.

Cod

Acum vă voi arăta un exemplu simplu de cod în limbajul Go. În acest exemplu am utilizat mldsa din 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    // Generăm cheia 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    // Generăm semnătura.
22    // De menționat: la versiunea actuală din 22 decembrie 2024, dacă crypto.Hash(0) nu este utilizat, apare o eroare.
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    // Apelăm scheme-ul cheii publice pentru a efectua verificarea.
32	ok := pub.Scheme().Verify(pub, message, signature, nil)
33	fmt.Println(ok)
34}
13228 oaSaOA-...
2true

Valoarea semnăturii a fost omisă deoarece este prea lungă. Dacă doriți versiunea integrală, puteți rula codul pe playground.

Deși este codificată în base64, dimensiunea de 3228 de octeți este probabil destul de incomodă. Gândindu-mă că în curând, pentru a ne apăra de computerele cuantice, ar putea fi nevoie să schimbăm semnături de această dimensiune, această perspectivă mă face să mă simt cam îngrijorat...

MLKEM

Structură

MLKEM este un mecanism de încapsulare a cheii (Key Encapsulation Mechanism). KEM este un algoritm care, folosind criptografia cu cheie publică, permite generarea unei chei partajate între două părți. Mecanismul de schimb de chei MLKEM parcurge următoarele etape:

  1. Încapsularea cheii: expeditorul utilizează cheia publică a destinatarului pentru a genera un mesaj criptat (cipher text) și o cheie partajată (shared key). Acest mesaj criptat este transmis inițial destinatarului, pentru a fi utilizat de acesta.
  2. Decapsularea cheii: destinatarul utilizează cheia sa privată pentru a extrage cheia partajată din mesajul criptat.

MLKEM are în total 3 parametri: MLKEM-512, MLKEM-768 și MLKEM-1024. Cu cât valoarea este mai mică, cu atât cheile și textul criptat sunt mai mici; cu cât valoarea este mai mare, cu atât cheile și textul criptat sunt mai lungi, iar nivelul de securitate este mai ridicat.

Cod

MLKEM urmează să fie adăugat în Go 1.24, prin urmare, în momentul de față, am utilizat go 1.24rc1, care este disponibilă.

 1package main
 2
 3import (
 4	"crypto/mlkem"
 5	"encoding/base64"
 6	"fmt"
 7)
 8
 9func main() {
10    // Generăm PrivateKey-ul destinatarului.
11	receiverKey, err := mlkem.GenerateKey1024()
12	if err != nil {
13		panic(err)
14	}
15
16    // În MLKEM se utilizează termenul EncapsulationKey, nu PublicKey.
17	receiverPubKey := receiverKey.EncapsulationKey()
18
19    // Pentru simplitate, am duplicat EncapsulationKey pentru a demonstra că poate fi extras prin Bytes() și reutilizat prin NewEncapsulationKeyX.
20    // Desigur, în practică, acest pas înseamnă că expeditorul transformă în obiect cheia EncapsulationKey a destinatarului, publicată anterior 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 duplicat intenționat cheia privată a destinatarului pentru a demonstra salvarea și recuperarea acesteia.
30	clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31	if err != nil {
32		panic(err)
33	}
34
35    // Destinatarul utilizează cheia privată pentru a efectua Decapsulate asupra textului criptat și generează 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, se poate constata că se generează chei partajate de aceeași dimensiune!

Acest cod poate fi verificat și pe playground.

Concluzie

Specificațiile fiecărui algoritm, nivelul de securitate, precum și dimensiunile cheii private, cheii publice, semnăturii sau textului criptat pot fi rezumate astfel. Fiecare dintre ele se remarcă, fără a fi nevoie de scuze pentru numele PQC, printr-o dimensiune considerabilă.

AlgoritmNivel de securitate NISTDimensiune cheie privatăDimensiune cheie publicăDimensiune semnătură/text criptat
ML-DSA-4422,5601,3122,420
ML-DSA-6534,0321,9523,309
ML-DSA-8754,8962,5924,627
ML-KEM-51211,632800768
ML-KEM-76832,4001,1841,088
ML-KEM-102453,1681,5681,568

Datorită acestor algoritmi, ne așteptăm să putem utiliza un internet suficient de sigur și în prezența computerelor cuantice, însă, din cauza dimensiunilor relativ mai mari ale cheilor și ale semnăturilor/textelor criptate, un volum mai mare de calcule pare inevitabil.

Totuși, Go implementează eficient fiecare dintre acești algoritmi, așa că sperăm că ei vor fi utilizați activ, în locurile potrivite, pentru a vă proteja securitatea!