GoSuda

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

By snowmerak views ...

Á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:

  1. Aláírás generálása: a privát kulccsal aláírást hozunk létre az üzenethez
  2. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. 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.
  2. 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.

AlgoritmusNIST biztonsági szintPrivát kulcs méreteNyilvános kulcs méreteAláírás/titkosított szöveg mérete
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

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!