Tapasztalataim az MLDSA és az MLKEM Go nyelvben történő használatáról

Áttekintés
Háttér
Már régóta úgy tekintenek a kvantumszámítógépek gyors számításaira, mint az általánosan használt titkosítási rendszerekre leselkedő fenyegetésre. A hagyományos RSA és ECC például azért lehet sebezhető, mert a kvantumszámítógépek ilyen számítási képessége feltörheti őket. Néhány éve azonban, amikor a kvantumszámítógép fogalma kezdett kézzelfoghatóvá válni, elkezdték kutatni és fejleszteni az alternatíváikat, a NIST pedig folytatta a PQC (posztkvantum kriptográfia) szabványosítását.
MLDSA és MLKEM
A NIST végül 2024 augusztusában szabványként fogadta el a CRYSTALS-Kyber és a CRYSTALS-Dilithium alapján készült MLKEM-et és MLDSA-t. Mindkét algoritmus az MLWE (Module Learning with Errors) problémán alapul. Ezt a típusú megoldást rácsalapú kriptográfiának nevezzük.
A rácsalapú kriptográfia, ahogy a neve is mutatja, olyan titkosítási rendszer, amely a rácsokon értelmezett matematikai problémák nehézségén alapul. Bár nincs mély matematikai tudásom erről, egy mondatban összefoglalva ez a zajos lineáris egyenletrendszer megoldása moduláris rácsban probléma. Hogy mennyire nehéz, azt nehéz elképzelni, de állítólag ezek a problémák a kvantumszámítógépekkel sem oldhatók meg.
MLDSA
Először tehát az MLDSA-ról tanulunk.
Felépítés
Ahogy a nevéből is látszik, az MLDSA egy aszimmetrikus aláírási algoritmus, amely összesen a következő két lépésből áll:
- Aláírás generálása: a privát kulccsal aláírást hozunk létre az üzenethez
- Aláírás ellenőrzése: a nyilvános kulcs segítségével ellenőrizzük a generált aláírás érvényességét
Az MLDSA-nak emellett a következő 4 tulajdonsága van:
- Erős létezésbeli hamisíthatatlanság (strong existential unforgeability): egy aláírás és a nyilvános kulcs alapján nem lehet más, érvényes aláírást létrehozni.
- Választott üzenetes támadás (chosen message attack): egyetlen üzenet aláírásával sem lehet a nyilvános kulcs birtokában új, érvényes aláírást létrehozni.
- Oldalcsatornás támadás (side-channel attack): aláírás során mindig új véletlen értékeket és az üzenetből származtatott pszeudovéletlen értékeket használ, ezért a biztonsága magas.
- Tartomány-szétválasztás (domain separation): különböző paraméterekhez különböző seedeket használ, ezzel megelőzve az ismétlődésből adódó biztonsági problémákat.
Kód
Most bemutatok egy egyszerű Go nyelvű példakódot. Ebben a példában a cloudflare/circl csomag mldsa moduljátt használtam.
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 // mldsa44 specifikációval generálunk kulcsot.
14 pub, priv, err := mldsa44.GenerateKey(rand.Reader)
15 if err != nil {
16 panic(err)
17 }
18
19 message := []byte("Hello, World!")
20
21 // Aláírást generálunk.
22 // Fontos megjegyezni, hogy 2024. december 22-i verzió szerint a crypto.Hash(0) használata nélkül hiba lép fel.
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 // A nyilvános kulcs scheme metódusát hívva elvégezzük az ellenőrzést.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
A aláírás értéke túl hosszú, ezért kihagytam. Ha teljes egészében szeretnéd látni, futtasd le a playground oldalon.
Bár base64-gyel kódoltuk, a 3228 bájt elég nagy méret, ami kissé terhesnek tűnhet. Ha belegondolunk, hogy a közeljövőben a kvantumszámítógépek ellen védő aláírásokat kell majd cserélnünk ekkora méretben, az valóban kissé nyomasztó.
MLKEM
Felépítés
Az MLKEM egy kulcsbeágyazási mechanizmus (Key Encapsulation Mechanism, KEM). A KEM olyan algoritmus, amely nyilvános kulcsú titkosítást használva lehetővé teszi, hogy két fél között közös kulcs jöjjön létre. Az MLKEM kulcscsere-mechanizmusa a következő lépésekből áll:
- Kulcsbeágyazás: a küldő a címzett nyilvános kulcsával létrehozza a titkosított üzenetet (ciphertext) és a megosztott kulcsot (shared key). Ezt a titkosított üzenetet a címzettnek továbbítja, hogy az felhasználhassa.
- Kulcs-kibontás: a címzett a saját privát kulcsával kinyeri a megosztott kulcsot a titkosított üzenetből.
Az MLKEM-nek összesen 3 paramétere van: MLKEM-512, MLKEM-768 és MLKEM-1024. Minél kisebb a szám, annál kisebb kulcsokat és titkosított szöveget kapunk; minél nagyobb, annál hosszabb kulcsok és titkosított szövegek keletkeznek, és annál magasabb a biztonsági szint.
Kód
Az MLKEM a Go 1.24-ben várhatóan megjelenik, ezért a jelenleg használható go 1.24rc1 verziót alkalmaztam.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Létrehozzuk a címzett PrivateKey-ét.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // Az MLKEM-ben a PublicKey helyett az EncapsulationKey kifejezést használjuk.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Azért klónozom le, hogy bemutassam, hogyan lehet a kulcsot Bytes() és NewEncapsulationKeyX segítségével kinyerni és újra felhasználni.
20 // Valójában ez a folyamat úgy tekinthető, mint amikor a küldő szöveges formában nyilvánosan elérhető címzetti EncapsulationKey-ből objektumot hoz létre.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // Az Encapsulate segítségével a küldő létrehozza a titkosított szöveget és a megosztott kulcsot.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Azért klónozom le a címzett privát kulcsát, hogy bemutassam, hogyan lehet tárolni és visszaolvasni.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // A címzett a privát kulcsával Decapsulate-eli a titkosított szöveget, és így létrehozza a másik megosztott kulcsot.
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=
Ennek eredményeként láthatjuk, hogy azonos méretű megosztott kulcs jön létre!
Ez a kód a playground oldalon is megtekinthető.
Következtetés
Az egyes algoritmusok specifikációi, biztonsági szintjei, valamint a privát kulcsok, nyilvános kulcsok, aláírások és titkosított szövegek méretei a következőképpen foglalhatók össze. Mindegyikük méltó a PQC elnevezéshez, nagy méretekkel büszkélkedve.
| Algoritmus | NIST biztonsági szint | Privát kulcs mérete | Nyilvános kulcs mérete | Aláírás/titkosított szöveg mérete |
|---|---|---|---|---|
| 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 |
Ezekkel az algoritmusokkal reméljük, hogy a kvantumszámítógépek korában is elég biztonságos lesz az internet, azonban a megnövekedett kulcs- és aláírás-/titkosítottszöveg-méretek miatt várhatóan több számításra lesz szükség, amit nem lehet elkerülni.
Mindazonáltal a Go nyelv hatékonyan implementálja ezeket az algoritmusokat, ezért bízom benne, hogy megfelelő helyeken aktívan hozzájárulnak majd a biztonságod védelméhez!