GoSuda

Uso de MLDSA y MLKEM en Go Language

By snowmerak
views ...

Resumen

Antecedentes

Desde hace bastante tiempo, la rápida capacidad computacional de las computadoras cuánticas ha sido percibida como una amenaza para los sistemas criptográficos existentes. Esto se debe a que algoritmos como RSA o ECC podrían ser descifrados por la capacidad computacional de las computadoras cuánticas. Sin embargo, hace varios años, a medida que el concepto de computadoras cuánticas comenzó a materializarse, se empezaron a investigar y desarrollar alternativas, y el NIST ha estado trabajando en la estandarización de PQC (Criptografía Resistente a la Cuántica).

MLDSA y MLKEM

Finalmente, el NIST adoptó MLKEM y MLDSA, basados en CRYSTALS-Kyber y CRYSTALS-Dilithium, como estándares en agosto de 2024. Ambos algoritmos operan basándose en un problema llamado MLWE (Module Learning with Errors). A este formato lo denominamos criptografía basada en retículos.

La criptografía basada en retículos es, como su nombre indica, un sistema criptográfico que se basa en la dificultad de un problema matemático en un retículo. Aunque no poseo un conocimiento matemático profundo al respecto, en una frase se describe como "el problema de resolver ecuaciones lineales con ruido en un retículo modular". No se percibe cuán difícil es, pero se dice que tales problemas son tan difíciles que ni siquiera una computadora cuántica puede resolverlos.

MLDSA

A continuación, examinaremos MLDSA.

Composición

MLDSA, como su nombre indica, es un algoritmo de firma asimétrica que consta de las siguientes 2 etapas:

  1. Generación de firma: Crea una firma para un mensaje utilizando una clave privada.
  2. Verificación de firma: Verifica la validez de la firma generada utilizando una clave pública.

Y MLDSA tiene las siguientes 3 características:

  1. strong existential unforgeability: No se puede generar otra firma válida a partir de una firma y una clave pública.
  2. chosen message attack: No se puede generar una nueva firma válida a partir de la clave pública con la firma de cualquier mensaje.
  3. side-channel attack: La seguridad es alta al utilizar continuamente nuevos valores aleatorios y valores pseudoaleatorios derivados del mensaje durante la firma.
  4. domain separation: Previene problemas de seguridad repetitivos al usar diferentes seed para diferentes parámetros.

Código

A continuación, se muestra un ejemplo de código simple en Go. Este ejemplo utiliza 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 la firma.
22    // Una cosa a tener en cuenta es que, a partir de la versión actual 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    // Llama al esquema de la clave pública para realizar la verificación.
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 y se ha omitido. Si desea ver el texto completo, ejecútelo en playground.

Aunque esté codificado en base64, que el resultado sea de 3228 bytes podría ser un poco abrumador. Es un poco preocupante pensar que pronto tendremos que intercambiar un tamaño tan grande de firmas para contrarrestar las computadoras cuánticas.

MLKEM

Composición

MLKEM es un mecanismo de encapsulación de claves (Key Encapsulation Mechanism). Un KEM es un algoritmo que permite generar una clave compartida entre dos partes utilizando criptografía de clave pública. El mecanismo de intercambio de claves de MLKEM sigue el siguiente proceso:

  1. Encapsulación de clave: El remitente utiliza la clave pública del receptor para generar un mensaje cifrado (cipher text) y una clave compartida (shared key). Este mensaje cifrado se entrega inicialmente al receptor para su uso.
  2. Desencapsulación de clave: El receptor utiliza su clave privada para extraer la clave compartida del mensaje cifrado.

MLKEM tiene un total de 3 parámetros. Existen MLKEM-512, MLKEM-768 y MLKEM-1024; cuanto menor sea el número, menores serán la clave y el texto cifrado resultantes, y cuanto mayor sea el número, más largas serán la clave y el texto cifrado, y mayor será el nivel de seguridad.

Código

MLKEM se agregará en Go 1.24, por lo que se utilizó la versión actual de Go 1.24rc1.

 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    // Se clonó para demostrar que la clave se puede extraer y reutilizar fácilmente con Bytes() y NewEncapsulationKeyX de EncapsulationKey.
20    // Por supuesto, en un escenario real, este proceso se vería como el remitente creando un objeto a partir de la clave EncapsulationKey del receptor que estaba públicamente disponible como texto.
21	clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22	if err != nil {
23		panic(err)
24	}
25
26    // El remitente genera el texto cifrado y la clave compartida con Encapsulate.
27	cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29    // Cloné la clave privada del receptor para mostrar cómo se guarda y se recupera.
30	clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31	if err != nil {
32		panic(err)
33	}
34
35    // El receptor utiliza su clave privada para desencapsular 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, se puede confirmar que se genera una clave compartida del mismo tamaño!

Este código también se puede verificar en playground.

Conclusión

Las especificaciones, los niveles de seguridad, y los tamaños de clave privada, clave pública, firma o texto cifrado de cada algoritmo se pueden resumir de la siguiente manera. Cada uno de ellos exhibe tamaños considerables, dignos del nombre PQC.

AlgoritmoNivel de seguridad NISTTamaño de clave privadaTamaño de clave públicaTamaño de firma/texto cifrado
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

Con estos algoritmos, esperamos poder utilizar una Internet suficientemente segura incluso frente a las computadoras cuánticas; sin embargo, parece inevitable que la mayor cantidad de cálculos se deba al tamaño relativamente mayor de las claves y las firmas/cifrados.

Aun así, el lenguaje Go implementa eficazmente cada algoritmo, por lo que ¡esperamos que se utilice activamente para proteger su seguridad en los lugares apropiados!