L'utilizzo di MLDSA e MLKEM nel linguaggio Go
Panoramica
Contesto
Da tempo si riconosce che la rapida capacità di calcolo dei computer quantistici rappresenta una minaccia per i sistemi di crittografia esistenti. Questo perché algoritmi come RSA ed ECC potrebbero essere decifrati dalle capacità computazionali dei computer quantistici. Tuttavia, man mano che il concetto di computer quantistico è diventato più concreto negli ultimi anni, si è iniziato a ricercare e sviluppare alternative, e il NIST ha portato avanti la standardizzazione della PQC (crittografia resistente ai quanti).
MLDSA e MLKEM
Infine, nell'agosto 2024, il NIST ha adottato MLKEM e MLDSA, basati su CRYSTALS-Kyber e CRYSTALS-Dilithium, come standard. Entrambi gli algoritmi operano sulla base del problema MLWE (Module Learning with Errors). Questo tipo di formato è denominato crittografia basata su reticoli.
La crittografia basata su reticoli, come suggerisce il nome, è un sistema di crittografia basato sulla difficoltà di problemi matematici su un reticolo. Non ho una conoscenza matematica approfondita di questo argomento, ma in breve, si tratta di "risolvere un'equazione lineare con rumore in un reticolo di moduli". Non ho idea di quanto sia difficile, ma si dice che questo problema sia così difficile da non poter essere risolto nemmeno con un computer quantistico.
MLDSA
Ora, esaminiamo prima MLDSA.
Struttura
MLDSA, come suggerisce il nome, è un algoritmo di firma asimmetrica e prevede un totale di 2 fasi:
- Generazione della firma: generazione di una firma per un messaggio utilizzando la chiave privata.
- Verifica della firma: verifica della validità della firma generata utilizzando la chiave pubblica.
Inoltre, MLDSA ha le seguenti 3 caratteristiche:
- strong existential unforgeability: non è possibile generare un'altra firma valida con una firma e una chiave pubblica.
- chosen message attack: non è possibile generare una nuova firma valida con una chiave pubblica, anche con firme per qualsiasi messaggio.
- side-channel attack: la sicurezza è elevata perché durante la firma vengono utilizzati continuamente nuovi valori casuali e valori pseudo-casuali derivati dal messaggio.
- domain separation: impedisce problemi di sicurezza ripetitivi, utilizzando seed diversi per parametri diversi.
Codice
Ora, ecco un semplice esempio di codice in linguaggio Go.
Questo esempio utilizza mldsa di 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 // Genera una chiave con le specifiche 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 // Genera una firma.
22 // Si noti che, a partire dalla versione del 22 dicembre 2024, si verifica un errore se non si utilizza 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 // Effettua la verifica chiamando lo schema della chiave pubblica.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
Il valore della firma è troppo lungo, quindi è stato omesso. Se volete vedere l'intero valore, eseguite il codice su playground.
Anche se è codificato in base64, 3228 byte sono un po' ingombranti.
Mi sembra un po' opprimente pensare che dovremo scambiare queste dimensioni di firma per contrastare i computer quantistici nel prossimo futuro.
MLKEM
Struttura
MLKEM è un meccanismo di incapsulamento della chiave (Key Encapsulation Mechanism). KEM è un algoritmo che consente a due parti di generare una chiave condivisa utilizzando la crittografia a chiave pubblica. Il meccanismo di scambio di chiavi di MLKEM segue i seguenti passaggi:
- Incapsulamento della chiave: il mittente utilizza la chiave pubblica del destinatario per generare un messaggio crittografato (testo cifrato) e una chiave condivisa. Questo messaggio crittografato viene inizialmente trasmesso al destinatario per l'utilizzo.
- Decapsulamento della chiave: il destinatario utilizza la propria chiave privata per estrarre la chiave condivisa dal messaggio crittografato.
MLKEM ha un totale di 3 parametri. Esistono MLKEM-512, MLKEM-768 e MLKEM-1024. Più piccolo è il valore, più piccole saranno la chiave e il testo cifrato; più grande è il valore, più lunghe saranno la chiave e il testo cifrato e più elevato sarà il livello di sicurezza.
Codice
MLKEM sarà aggiunto a go 1.24, quindi abbiamo utilizzato go 1.24rc1, che è disponibile al momento.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Genera la PrivateKey del destinatario.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // In MLKEM, si utilizza il termine EncapsulationKey anziché PublicKey.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Ho clonato la chiave per mostrare brevemente che la EncapsulationKey può essere estratta e riutilizzata con Bytes() e NewEncapsulationKeyX.
20 // Naturalmente, nell'uso reale, si può considerare questo processo come il processo in cui il mittente converte in un oggetto la chiave EncapsulationKey del destinatario, che era stata pubblicata come testo.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // Il mittente genera il testo cifrato e la chiave condivisa con Encapsulate.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Ho deliberatamente clonato la chiave per dimostrare come la chiave privata del destinatario viene memorizzata e recuperata.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // Il destinatario utilizza la chiave privata per decapsulare il testo cifrato e generare un'altra chiave condivisa.
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=
In definitiva, possiamo confermare che viene generata una chiave condivisa della stessa dimensione!
Questo codice può essere verificato anche su playground.
Conclusione
Le specifiche di ciascun algoritmo, il livello di sicurezza e le dimensioni della chiave privata, della chiave pubblica, della firma o del testo cifrato possono essere riassunti come segue. Ognuno vanta dimensioni considerevoli, degne del nome PQC.
Algoritmo | Livello di sicurezza NIST | Dimensione chiave privata | Dimensione chiave pubblica | Dimensione firma/testo cifrato |
---|---|---|---|---|
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 |
Ci auguriamo che questi algoritmi ci consentano di utilizzare Internet in modo sufficientemente sicuro anche sui computer quantistici, ma sembra inevitabile che ci saranno più calcoli a causa delle dimensioni relativamente maggiori delle chiavi e delle firme/testi cifrati.
Tuttavia, poiché Go ha implementato efficacemente ciascun algoritmo, ci auguriamo che vengano utilizzati attivamente per proteggere la vostra sicurezza nei punti appropriati!