Uso de MLDSA e MLKEM na linguagem Go
Visão Geral
Contexto
Há já algum tempo, a capacidade de computação rápida dos computadores quânticos tem sido reconhecida como uma ameaça aos sistemas de criptografia existentes. Isso ocorre porque algoritmos como RSA e ECC podem ser decifrados pela capacidade computacional dos computadores quânticos. No entanto, à medida que o conceito de computadores quânticos começou a se materializar há alguns anos, alternativas começaram a ser pesquisadas e desenvolvidas, e o NIST tem trabalhado na padronização de PQC (Criptografia Resistente a Quântica).
MLDSA e MLKEM
Finalmente, em agosto de 2024, o NIST adotou MLKEM e MLDSA, baseados em CRYSTALS-Kyber e CRYSTALS-Dilithium, como padrões. Ambos os algoritmos operam com base no problema MLWE (Module Learning with Errors). Essa forma é chamada de criptografia baseada em reticulados.
A criptografia baseada em reticulados, como o nome sugere, é um sistema de criptografia baseado na dificuldade de problemas matemáticos em reticulados. Eu mesmo não tenho um conhecimento matemático profundo sobre o assunto, mas, resumidamente, é o problema de resolver equações lineares com ruído em um reticulado de módulos
. Não tenho ideia do quão difícil isso é, mas esse problema é considerado difícil o suficiente para não ser resolvido nem mesmo por computadores quânticos.
MLDSA
Então, vamos primeiro aprender sobre o MLDSA.
Composição
O MLDSA, como o nome sugere, é um algoritmo de assinatura assimétrica que passa por um total de duas etapas:
- Geração de assinatura: Geração de uma assinatura para uma mensagem usando uma chave privada.
- Verificação de assinatura: Verificação da validade da assinatura gerada usando uma chave pública.
Além disso, o MLDSA tem três características:
- forte não falsificabilidade existencial: Não é possível gerar outra assinatura válida com uma assinatura e uma chave pública.
- ataque de mensagem escolhida: Não é possível gerar uma nova assinatura válida com uma chave pública a partir de uma assinatura para qualquer mensagem.
- ataque de canal lateral: A segurança é alta porque novos valores aleatórios e valores pseudoaleatórios derivados da mensagem são usados continuamente ao assinar.
- separação de domínio: Evita problemas de segurança repetitivos, permitindo que diferentes seeds sejam usadas para diferentes parâmetros.
Código
Agora, vou mostrar um exemplo de código simples na linguagem Go. Este exemplo usou o 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 // Cria uma chave com a especificação mldsa44.
14 pub, priv, err := mldsa44.GenerateKey(rand.Reader)
15 if err != nil {
16 panic(err)
17 }
18
19 message := []byte("Olá, Mundo!")
20
21 // Gera uma assinatura.
22 // Um ponto a notar é que, de acordo com a versão atual em 22 de dezembro de 2024, um erro ocorre se não for 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 // Chama o esquema da chave pública para realizar a verificação.
32 ok := pub.Scheme().Verify(pub, message, signature, nil)
33 fmt.Println(ok)
34}
13228 oaSaOA-...
2true
O valor da assinatura foi omitido porque era muito longo. Se você quiser ver o texto completo, execute-o no playground.
Embora tenha sido codificado em base64, é um pouco oneroso ter 3228 bytes. É um pouco oneroso pensar que em breve talvez tenhamos que trocar este tamanho como assinatura contra computadores quânticos.
MLKEM
Composição
MLKEM é um Mecanismo de Encapsulamento de Chaves (Key Encapsulation Mechanism). KEM é um algoritmo que permite que duas partes gerem uma chave compartilhada usando criptografia de chave pública. O mecanismo de troca de chaves do MLKEM passa pelo seguinte processo:
- Encapsulamento de chave: O remetente usa a chave pública do destinatário para gerar uma mensagem criptografada (cipher text) e uma chave compartilhada. Essa mensagem criptografada é inicialmente entregue ao destinatário para uso.
- Desencapsulamento de chave: O destinatário usa sua chave privada para extrair a chave compartilhada da mensagem criptografada.
Existem um total de três parâmetros no MLKEM. Existem MLKEM-512, MLKEM-768 e MLKEM-1024. Quanto menor, menor a chave e o texto criptografado. Quanto maior, maior a chave e o texto criptografado e maior o nível de segurança.
Código
O MLKEM será adicionado ao Go 1.24, portanto, usei o Go 1.24rc1, que está disponível no momento.
1package main
2
3import (
4 "crypto/mlkem"
5 "encoding/base64"
6 "fmt"
7)
8
9func main() {
10 // Gera a PrivateKey do destinatário.
11 receiverKey, err := mlkem.GenerateKey1024()
12 if err != nil {
13 panic(err)
14 }
15
16 // No MLKEM, o termo EncapsulationKey é usado em vez de PublicKey.
17 receiverPubKey := receiverKey.EncapsulationKey()
18
19 // Foi clonado para mostrar que a chave pode ser extraída e reutilizada simplesmente com Bytes() e NewEncapsulationKeyX de EncapsulationKey.
20 // Claro, na realidade, se você o usar, esse processo será o processo em que o remetente cria a chave EncapsulationKey do destinatário que foi divulgada como texto como um objeto.
21 clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
22 if err != nil {
23 panic(err)
24 }
25
26 // O remetente gera texto criptografado e uma chave compartilhada com Encapsulate.
27 cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()
28
29 // Eu deliberadamente o clonei para mostrar o armazenamento e a recuperação da chave privada do destinatário.
30 clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
31 if err != nil {
32 panic(err)
33 }
34
35 // O destinatário usa a chave privada para Decapsulate o texto criptografado e gerar outra chave compartilhada.
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, você pode verificar que uma chave compartilhada do mesmo tamanho é gerada!
Este código também pode ser verificado no playground.
Conclusão
As especificações, os níveis de segurança e os tamanhos de chave privada, chave pública, assinatura ou texto cifrado de cada algoritmo podem ser resumidos da seguinte forma. Cada um tem um tamanho grande, o que não é nada vergonhoso para ser chamado de PQC.
Algoritmo | Nível de segurança NIST | Tamanho da chave privada | Tamanho da chave pública | Tamanho da assinatura/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 |
Espera-se que esses algoritmos nos permitam usar a Internet de forma segura, mesmo em computadores quânticos, mas parece inevitável que haverá mais cálculos devido ao aumento relativo do tamanho das chaves e assinaturas/textos cifrados.
Ainda assim, como cada algoritmo está implementado de forma eficaz na linguagem Go, espero que seja ativamente utilizado para proteger a sua segurança em locais adequados!