---
id: df2ea4cfe0d5ec72a92e1b3cfd4156f2
author: snowmerak
title: Go 语言中 MLDSA 与 MLKEM 的使用体验
description: 在 Go 语言中介绍后量子密码学 MLDSA 和 MLKEM 的使用方法，并阐述各算法的特点，同时提供代码示例。
language: zh
date: 2024-12-22T08:17:58.797764292Z
path: /blog/posts/usage-of-mldsa-and-mlkem-in-the-go-language-zb0442110
category: security
---

## 概述

### 背景

很久以前，量子计算机的快速运算就被视为对现有加密体系的威胁。这是因为传统的RSA和ECC等算法有可能因量子计算机的这种运算能力而被破解。然而，从几年前开始，量子计算机的概念逐渐变得具体可见，与之相应的替代方案开始得到研究和开发，NIST也一直在推进PQC（后量子密码）的标准化工作。

### MLDSA与MLKEM

最终，NIST于2024年8月采纳了基于CRYSTALS-Kyber和CRYSTALS-Dilithium的MLKEM和MLDSA作为标准。这两种算法基于MLWE（Module Learning with Errors）问题运行。这种形式的密码学，我们称之为基于格的密码学。

基于格的密码学，顾名思义，是一种基于格上数学问题之难解性的密码系统。我对此也没有深入的数学知识，但若用一句话概括，它是"在模格中求解带噪声的线性方程组的问题"。虽然难以想象其难度究竟有多大，但据说这类问题即使借助量子计算机也无法求解，难度极高。

## MLDSA

那么，我们先来了解一下MLDSA。

### 构成

正如其名称所示，MLDSA是一种非对称签名算法，共分为以下两个步骤。

1. 签名生成：使用私钥对消息生成签名
2. 签名验证：使用公钥确认所生成签名的有效性

此外，MLDSA具有以下3项特性。

1. strong existential unforgeability：无法仅凭一个签名和公钥生成另一个有效签名。
2. chosen message attack：即使获得任意消息的签名，也无法利用公钥生成新的有效签名。
3. side-channel attack：签名时持续使用新的随机值以及从消息派生的伪随机值，因此安全性较高。
4. domain separation：对不同参数使用不同的seed，以防止重复使用带来的安全问题。

### 代码

下面展示一个简单的Go语言示例代码。
本示例使用了[cloudflare/circl](github.com/cloudflare/circl)中的mldsa。

```go
package main

import (
	"crypto"
	"crypto/rand"
	"encoding/base64"
	"fmt"

	"github.com/cloudflare/circl/sign/mldsa/mldsa44"
)

func main() {
    // 使用mldsa44规范生成密钥。
	pub, priv, err := mldsa44.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	message := []byte("Hello, World!")

    // 生成签名。
    // 需要注意的一点是，截至2024年12月22日的当前版本，如果crypto.Hash(0)不是该值，则会发生错误。
	signature, err := priv.Sign(rand.Reader, message, crypto.Hash(0))
	if err != nil {
		panic(err)
	}

	encodedSignature := base64.URLEncoding.EncodeToString(signature)
	fmt.Println(len(encodedSignature), encodedSignature)

    // 调用公钥的scheme进行验证。
	ok := pub.Scheme().Verify(pub, message, signature, nil)
	fmt.Println(ok)
}
```

```
3228 oaSaOA-...
true
```

> 由于签名值过长，此处省略。如果想查看完整内容，请在[playground](https://go.dev/play/p/nqbUnKpFW3j)中运行。

虽然已经进行了base64编码，但输出结果达到3228字节，想必还是会让人感到有些负担。
想到今后我们可能需要收发这种大小的、用于对抗量子计算机的签名，确实让人有些担忧……

## MLKEM

### 构成

MLKEM是一种密钥封装机制（Key Encapsulation Mechanism）。KEM是一种利用公钥加密方式，使双方之间能够生成共享密钥的算法。MLKEM的密钥交换机制经过以下过程。

1. 密钥封装：发送方使用接收方的公钥生成加密消息（cipher text）和共享密钥（shared key）。该加密消息随后被传递给接收方，以供其使用。
2. 密钥解封：接收方使用自己的私钥从加密消息中提取共享密钥。

MLKEM共有3种参数，即MLKEM-512、MLKEM-768和MLKEM-1024。参数越小，密钥和密文越小；参数越大，密钥和密文越长，安全级别也越高。

### 代码

MLKEM将于Go 1.24中加入，因此在当前时点我们使用了可用的go 1.24rc1版本。

```go
package main

import (
	"crypto/mlkem"
	"encoding/base64"
	"fmt"
)

func main() {
    // 生成接收方的PrivateKey。
	receiverKey, err := mlkem.GenerateKey1024()
	if err != nil {
		panic(err)
	}

    // MLKEM中使用的术语是EncapsulationKey，而不是PublicKey。
	receiverPubKey := receiverKey.EncapsulationKey()

    // 为了简单地展示可以通过EncapsulationKey的Bytes()和NewEncapsulationKeyX提取密钥并再次使用，这里进行了复制。
    // 当然，在实际应用中，这一过程可以理解为发送方将以文本形式公开的接收方EncapsulationKey密钥转换为对象的过程。
	clonedReceiverPubKey, err := mlkem.NewEncapsulationKey1024(receiverPubKey.Bytes())
	if err != nil {
		panic(err)
	}

    // 发送方使用Encapsulate生成加密文本和共享密钥。
	cipherText, SenderSharedKey := clonedReceiverPubKey.Encapsulate()

    // 为了展示接收方私钥的保存与取出过程，特意进行了复制。
	clonedReceiverKey, err := mlkem.NewDecapsulationKey1024(receiverKey.Bytes())
	if err != nil {
		panic(err)
	}

    // 接收方使用私钥对加密文本进行Decapsulate，从而生成另一个共享密钥。
	sharedKeyReceiver, err := clonedReceiverKey.Decapsulate(cipherText)
	if err != nil {
		panic(err)
	}

	fmt.Println(base64.StdEncoding.EncodeToString(SenderSharedKey))
	fmt.Println(base64.StdEncoding.EncodeToString(sharedKeyReceiver))
}
```

```sh
Q1ciS818WFHTK7D4MTvsQvciMTGF+dSGqMllOxW80ew=
Q1ciS818WFHTK7D4MTvsQvciMTGF+dSGqMllOxW80ew=
```

由此可以确认，生成了相同大小的共享密钥！

该代码也可以在[playground](https://go.dev/play/p/n_cxNp435Qn?v=gotip)中查看。

## 结论

各算法的规范、安全级别以及私钥、公钥、签名或密文的大小可整理如下。它们各自都不负"PQC"之名，体积颇为可观。

| 算法        | NIST安全级别 | 私钥大小 | 公钥大小 | 签名/密文大小 |
| ----------- | -------------- | ------------ | ------------ | ---------------- |
| 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            |

借助这些算法，我们期待在量子计算机环境下也能使用足够安全的互联网，但相对增大的密钥和签名/密文尺寸，显然不可避免地会带来更多的运算开销。

尽管如此，Go语言已经对各算法进行了高效实现，因此我们期待它能在合适的场景中被积极应用，守护各位的安全！
