登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

mlkem测试后量子密钥交换

来源:17golang原创

时间:2026-11-15 04:23:20 362浏览 收藏

如果你要在 Go 里试用后量子密钥交换,先记住一个边界:crypto/mlkem 负责真正的 ML-KEM 密钥封装,crypto/mlkem/mlkemtest 只给测试提供去随机封装。前者用于建立共享密钥,后者用于让同一组测试输入得到可重复的密文和共享密钥。

Go 1.24 起标准库提供 ML-KEM-768 和 ML-KEM-1024。本文以官方更建议多数应用优先考虑的 ML-KEM-768 为例,串起生成密钥、公开封装密钥、封装、解封装和测试隔离。

一句话结论:生产代码调用 GenerateKey768、Encapsulate、Decapsulate;需要固定输入做 known-answer test 时,再把 mlkemtest.Encapsulate768 放进测试文件,绝不要把它当成生产封装入口。

先把 Go 版本和 ML-KEM 的角色对齐

crypto/mlkem 是密钥封装机制(KEM),它的结果是共享密钥与密文,不是直接接收业务明文的加密器。典型协议是:接收端生成密钥对,只把封装公钥发给发送端;发送端根据公钥封装出密文和共享密钥;接收端用私钥和密文恢复同一共享密钥,后续再把共享密钥交给 AEAD 或密钥派生流程。

官方资料:https://pkg.go.dev/crypto/mlkem、https://pkg.go.dev/crypto/mlkem/mlkemtest、https://go.dev/doc/go1.24。

如果项目仍低于 Go 1.24,导入 crypto/mlkem 会直接失败。不要为了绕过编译错误随意换成同名第三方包;先把工具链和 go.mod 的最低版本边界说清楚。

ML-KEM-768 的私钥、公开封装密钥、密文、解封装与共享密钥关系说明图
图1:ML-KEM-768 结构说明图,展示公开封装密钥、密文和两端共享密钥的关系;这是静态说明图,不是运行截图。

用 ML-KEM-768 完成一次共享密钥建立

下面的示例把 Alice 视为持有解封装私钥的一方,把 Bob 视为拿到公开封装密钥的一方。Bob 只需要发送密文,双方最终比较得到的 32 字节共享密钥。

package main

import (
	"bytes"
	"crypto/mlkem"
	"fmt"
)

func establishSharedKey() error {
	// 接收端生成 ML-KEM-768 私钥,并从私钥导出可公开发送的封装公钥。
	dk, err := mlkem.GenerateKey768()
	if err != nil {
		return fmt.Errorf("生成 ML-KEM-768 密钥失败:%w", err)
	}
	publicBytes := dk.EncapsulationKey().Bytes()

	// 发送端只解析封装公钥;私钥不会离开接收端。
	ek, err := mlkem.NewEncapsulationKey768(publicBytes)
	if err != nil {
		return fmt.Errorf("解析封装公钥失败:%w", err)
	}
	senderKey, ciphertext := ek.Encapsulate() // 生产路径使用安全随机源。

	// 接收端用私钥解封装;长度错误会返回 error,不能忽略。
	receiverKey, err := dk.Decapsulate(ciphertext)
	if err != nil {
		return fmt.Errorf("解封装失败:%w", err)
	}
	if !bytes.Equal(senderKey, receiverKey) {
		return fmt.Errorf("两端共享密钥不一致")
	}
	fmt.Printf("shared key=%d bytes, ciphertext=%d bytes\n", len(senderKey), len(ciphertext))
	return nil
}

func main() {
	if err := establishSharedKey(); err != nil {
		panic(err) // 示例把错误交给顶层;服务代码应返回并记录上下文。
	}
}

ML-KEM-768 的共享密钥是 32 字节,封装公钥是 1184 字节,密文是 1088 字节。这里的尺寸适合放进协议设计和传输预算,但不要把“字节数正确”当成密钥有效性的唯一判断;解封装仍应检查返回的错误,并在协议层处理身份认证和重放问题。

用 mlkemtest 写可重复测试

生产封装会从安全随机源取数,同一个公钥每次得到的密文通常不同。测试若只断言“能解封装”,可以直接使用生产 API;若要复现标准测试向量或定位实现差异,则需要固定封装阶段的随机输入。官方的 mlkemtest.Encapsulate768 接受 32 字节随机输入,并明确限制为 known-answer tests。

crypto/mlkem 生产路径与 crypto/mlkem/mlkemtest 测试路径的边界说明图
图2:测试边界说明图,对比生产封装与 mlkemtest 的固定随机输入;这是静态说明图,不是测试结果截图。
package mlkem_test

import (
	"bytes"
	"crypto/mlkem"
	"crypto/mlkem/mlkemtest"
	"testing"
)

func TestMLKEM768DeterministicEncapsulation(t *testing.T) {
	// 固定测试向量只用于测试;生产代码不能复用固定随机输入。
	seed := bytes.Repeat([]byte{0x27}, mlkem.SeedSize)
	random := bytes.Repeat([]byte{0x42}, 32) // mlkemtest 要求 32 字节封装随机输入。
	dk, err := mlkem.NewDecapsulationKey768(seed)
	if err != nil {
		t.Fatalf("构造测试私钥失败:%v", err)
	}
	ek := dk.EncapsulationKey()

	shared1, ciphertext1, err := mlkemtest.Encapsulate768(ek, random)
	if err != nil {
		t.Fatalf("第一次去随机封装失败:%v", err)
	}
	shared2, ciphertext2, err := mlkemtest.Encapsulate768(ek, random)
	if err != nil {
		t.Fatalf("第二次去随机封装失败:%v", err)
	}
	if !bytes.Equal(shared1, shared2) || !bytes.Equal(ciphertext1, ciphertext2) {
		t.Fatal("固定测试输入没有得到稳定结果")
	}

	decoded, err := dk.Decapsulate(ciphertext1)
	if err != nil {
		t.Fatalf("测试密文解封装失败:%v", err)
	}
	if !bytes.Equal(shared1, decoded) {
		t.Fatal("封装与解封装得到的共享密钥不一致")
	}
}

示例中的固定种子只是为了展示测试 API 的边界;正式 known-answer test 应替换成项目维护的、来源明确的测试向量,并把期望密文和共享密钥也写入断言。不要因为测试需要可重复,就把固定字节数组带到线上代码里。

检查错误、长度和密钥生命周期

排查 ML-KEM 测试失败时,我会按三层看:第一层是工具链是否至少为 Go 1.24;第二层是封装公钥是否由同一参数集解析,例如 768 的公钥不能交给 1024 的解析函数;第三层是密文长度和解封装错误是否被保留。官方文档说明,长度不正确的密文会返回错误;长度正确但内容无效时,解封装可能返回一个与发送端不匹配的共享密钥,因此协议还需要认证或失败检测。

  • 不要打印私钥、共享密钥或完整密文;日志只记录阶段和长度。
  • 不要把 mlkemtest 的固定随机输入用于生产。
  • 不要只比较公钥、密文长度就判定交换成功,最终要比较双方共享密钥或进入经过认证的后续协议。
  • 如果需要和 TLS 对接,应单独研究 Go 的混合后量子握手能力,不要把裸 ML-KEM API 直接当成 TLS 配置。

一张表记住生产和测试的分工

目标推荐 API随机性放置位置
线上建立共享密钥GenerateKey768、Encapsulate、Decapsulate使用安全随机源生产包
复现封装结果mlkemtest.Encapsulate768固定 32 字节测试输入测试文件
检查协议边界长度、错误、共享密钥比较不记录秘密值测试与运行日志

最后再强调一次:ML-KEM 的职责是协商共享密钥。真正保护业务数据时,还要把密钥交给合适的认证加密或密钥派生组件,并为对端身份、协议版本和失败重试设计独立的约束。

常见问题

Go 1.23 能直接导入 crypto/mlkem 吗?

不能。这个标准库包从 Go 1.24 开始提供,项目应先升级工具链或明确采用其他经过评估的实现。

mlkemtest 可以放在业务代码里做固定密钥交换吗?

不建议。它的文档用途是 known-answer tests;业务代码应使用生产 API 的安全随机封装。

为什么密文长度正确仍可能得到不同共享密钥?

ML-KEM 的解封装对“长度正确但内容无效”的密文可能产生不匹配的共享密钥,而不是必然返回错误。协议必须把认证和结果一致性纳入整体设计。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>