Uso de MLDSA y MLKEM en el lenguaje Go
Resumen
Antecedentes
Desde hace bastante tiempo, la rápida computación de los ordenadores cuánticos se ha reconocido como una amenaza para los sistemas de cifrado existentes. Esto se debe a que los algoritmos existentes como RSA o ECC podrían ser descifrados debido a la capacidad de cálculo de los ordenadores cuánticos. Sin embargo, a medida que el concepto de ordenador cuántico comenzó a materializarse hace varios años, se iniciaron investigaciones y desarrollos sobre alternativas, y el NIST ha estado llevando a cabo la estandarización de PQC (criptografía resistente a la cuántica).
MLDSA y MLKEM
Finalmente, en agosto de 2024, el NIST adoptó MLKEM y MLDSA, basados en CRYSTALS-Kyber y CRYSTALS-Dilithium, respectivamente, como estándares. Ambos algoritmos operan basándose en el problema MLWE (Module Learning with Errors). A este formato lo llamamos criptografía basada en celosías.
La criptografía basada en celosías, como su nombre indica, es un sistema de cifrado basado en la dificultad de los problemas matemáticos en una celosía. Aunque no tengo un conocimiento matemático profundo sobre esto, se puede resumir en una línea como el problema de resolver ecuaciones lineales con ruido en una celosía modular
. No tengo una idea de lo difícil que es, pero se dice que este problema es tan difícil que ni siquiera un ordenador cuántico puede resolverlo.
MLDSA
Primero, veamos MLDSA.
Composición
MLDSA, como su nombre indica, es un algoritmo de firma asimétrica que consta de un total de 2 pasos:
- Generación de firma: Generar una firma para un mensaje utilizando una clave privada.
- Verificación de firma: Verificar la validez de la firma generada utilizando una clave pública.
Además, MLDSA tiene las siguientes 4 características:
- strong existential unforgeability: No se puede generar otra firma válida con una firma y una clave pública.
- chosen message attack: No se puede generar una nueva firma válida con una clave pública a partir de una firma para cualquier mensaje.
- side-channel attack: La seguridad es alta porque utiliza continuamente nuevos valores aleatorios y valores pseudoaleatorios derivados del mensaje al firmar.
- domain separation: Evita problemas de seguridad recurrentes al utilizar diferentes seeds para diferentes parámetros.
Código
Ahora, veamos un ejemplo sencillo de código en lenguaje Go. En este ejemplo, usamos mldsa de 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 clave con la especificación 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 // Una cosa a tener en cuenta es que, a partir de la versión del 22 de diciembre de 2024, se produce un error si no es 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 // Verifica usando el scheme de la clave pública.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
El valor de la firma es demasiado largo, así que lo omití. Si desea ver el texto completo, ejecútelo en playground.
Aunque está codificado en base64, es un poco abrumador que salga 3228 bytes. Es un poco abrumador pensar que pronto podríamos tener que intercambiar este tamaño como una firma contra los ordenadores cuánticos...
MLKEM
Composición
MLKEM es un Mecanismo de Encapsulación de Clave (Key Encapsulation Mechanism). Un KEM es un algoritmo que permite a dos partes generar una clave compartida utilizando un método de cifrado de clave pública. El mecanismo de intercambio de claves de MLKEM sigue el siguiente proceso:
- Encapsulación de clave: El emisor utiliza la clave pública del receptor para generar un mensaje cifrado (texto cifrado) y una clave compartida. Este mensaje cifrado se envía inicialmente al receptor para que lo utilice.
- Desencapsulación de clave: El receptor utiliza su clave privada para extraer la clave compartida del mensaje cifrado.
Existen un total de 3 parámetros en MLKEM. Existen MLKEM-512, MLKEM-768 y MLKEM-1024. Cuanto menor sea el número, menor será la clave y el texto cifrado, y cuanto mayor sea el número, más larga será la clave y el texto cifrado, y mayor será el nivel de seguridad.
Código
MLKEM se añadirá en go 1.24, así que hemos utilizado go 1.24rc1, que se puede utilizar en este momento.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Genera la PrivateKey del receptor.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // En MLKEM, se utiliza el término EncapsulationKey en lugar de PublicKey.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Lo cloné para mostrar que se pueden extraer y volver a utilizar las claves simplemente con Bytes() y NewEncapsulationKeyX de EncapsulationKey.
20 // Por supuesto, si se usa en la realidad, este proceso sería el proceso en el que el remitente crea un objeto a partir de la clave EncapsulationKey del receptor que se había publicado en texto.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // El remitente genera un texto cifrado y una clave compartida con Encapsulate.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Lo cloné deliberadamente para mostrar cómo guardar y extraer la clave privada del receptor.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // El receptor utiliza la clave privada para Decapsulate el texto cifrado y generar otra clave compartida.
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=
¡Como resultado, podemos confirmar que se genera una clave compartida del mismo tamaño!
Este código también se puede consultar en playground.
Conclusión
Las especificaciones, el nivel de seguridad y el tamaño de la clave privada, la clave pública, la firma o el texto cifrado de cada algoritmo se pueden resumir de la siguiente manera. Cada uno tiene un tamaño considerable, lo que hace honor al nombre de PQC.
Algoritmo | Nivel de seguridad NIST | Tamaño de clave privada | Tamaño de clave pública | Tamaño de firma/texto cifrado |
---|---|---|---|---|
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 |
Esperamos que estos algoritmos nos permitan utilizar una Internet lo suficientemente segura incluso en ordenadores cuánticos, pero parece inevitable que haya más cálculos debido al aumento relativo del tamaño de las claves y las firmas/textos cifrados.
Aún así, dado que cada algoritmo está implementado eficazmente en el lenguaje Go, esperamos que se utilice activamente para proteger su seguridad en las ubicaciones adecuadas.