Go crypto/mlkem 怎样封装后量子密钥协商
来源:17golang原创
时间:2026-10-20 05:15:48 406浏览 收藏
Go crypto/mlkem 可以把后量子密钥建立封装成三个职责:接收侧生成并保管解封装私钥,发送侧使用公开封装密钥生成共享秘密及密文,接收侧用私钥解封装密文。网络上传的是公钥与封装密文,不是共享秘密。它不是业务明文加密函数,也不是带身份认证的完整握手协议。
Go 官方地址:https://go.dev/;标准库地址:https://pkg.go.dev/crypto/mlkem。
标准库提供的是 KEM 原语
Go 1.24 将 crypto/mlkem 加入标准库,实现 FIPS 203 定义的 ML-KEM;本文以 ML-KEM-768 为例,参见 Go 1.24 发布说明。示例使用该版本已有的具体类型接口,不依赖后续新增的通用封装接口。
KEM 的目标是建立共享秘密。NIST 的 FIPS 203 标准说明将它作为密钥封装机制,而非消息签名机制。若把业务字符串塞给 Encapsulate,接口本身就不匹配:该方法不接收业务明文,而是产生随机共享秘密及其封装密文。
先区分公钥、私钥与封装密文
在本文封装中,Receiver 持有 dk,PublicKey 只导出封装公钥,SealKey 的结果明确区分 ciphertext 与 secret。GenerateKey768 提供随机密钥生成,EncapsulationKey().Bytes() 提供公开编码;解封装私钥与共享秘密都要保密,详见 标准库接口文档。
| 材料 | 本文变量或入口 | 放置位置 |
|---|---|---|
| 解封装私钥 | dk | 接收侧内部 |
| 公开封装密钥 | PublicKey | 可公开,但接收方身份必须另行确认 |
| 封装密文 | ciphertext | 发给接收侧 |
| 共享秘密 | secret | 各端本地,不写日志 |

用函数封装一次密钥建立
下面是同进程教学示例,用于理解两侧职责,没有网络层、身份认证或业务数据加密。Receiver 不允许零值直接使用,应从 NewReceiver 创建;示例未在本文环境执行,末尾文案仅为代码设定的成功分支。
package main
import (
"bytes"
"crypto/mlkem"
"errors"
"fmt"
)
// Receiver 只在接收侧持有解封装私钥,不把它装进网络消息。
type Receiver struct {
dk *mlkem.DecapsulationKey768
}
func NewReceiver() (*Receiver, error) {
dk, err := mlkem.GenerateKey768()
if err != nil {
return nil, fmt.Errorf("生成密钥: %w", err)
}
return &Receiver{dk: dk}, nil
}
// PublicKey 只导出公开封装密钥;不要改成 dk.Bytes()。
func (r *Receiver) PublicKey() []byte {
return r.dk.EncapsulationKey().Bytes()
}
// SealKey 返回可传输密文与本地秘密,两者不能混成一个网络包。
func SealKey(public []byte) (ciphertext, secret []byte, err error) {
ek, err := mlkem.NewEncapsulationKey768(public)
if err != nil {
return nil, nil, fmt.Errorf("解析封装公钥: %w", err)
}
// 标准库返回顺序是秘密在前、密文在后,此处显式重排。
shared, ct := ek.Encapsulate()
return ct, shared, nil
}
func (r *Receiver) OpenKey(ciphertext []byte) ([]byte, error) {
// 输入长度检查在原语中完成;错误逐层返回,不生成替代密钥。
secret, err := r.dk.Decapsulate(ciphertext)
if err != nil {
return nil, fmt.Errorf("解封装: %w", err)
}
return secret, nil
}
func demo() error {
receiver, err := NewReceiver()
if err != nil {
return err
}
ct, senderSecret, err := SealKey(receiver.PublicKey())
if err != nil {
return err
}
receiverSecret, err := receiver.OpenKey(ct)
if err != nil {
return err
}
// 仅在同进程教学示例比较两端结果,不打印或传输秘密。
if !bytes.Equal(senderSecret, receiverSecret) {
return errors.New("示例密钥不一致")
}
return nil
}
func main() {
if err := demo(); err != nil {
fmt.Println("示例失败:", err)
return
}
fmt.Println("示例密钥一致")
}
这里特意将封装函数命名为 SealKey,而不是 EncryptMessage,避免调用方把它当成通用加密器。标准库 Encapsulate 返回 sharedKey、ciphertext,本文函数则返回 ciphertext、secret、error,因此中间使用具名局部变量,避免反向传输秘密。PublicKey 中的导出方法也不能与私钥的 Bytes 混用。
解封装无错误不等于身份可信
Decapsulate 对长度错误返回 error,但长度正确而无效的密文可能不报错,而是得到与发送侧不同的密钥。这是文档明示的行为,不能把 err == nil 当成密文完整性或发送者身份已经得到确认。公开密钥解析成功,同样只说明编码有效,不说明它属于期望的接收者。
在这份教学封装里,NewReceiver 处理生成错误,SealKey 处理公钥解析错误,OpenKey 处理解封装错误,demo 只比较本地两端结果。应用协议的认证与确认不在这几个函数内;实际系统应使用经过审查的认证协议,不应把这个示例直接当作生产握手替代品。

迁移时先固定参数与职责
迁移的第一步不是替换所有旧密码接口,而是确定两端都支持同一参数集和编码,并让公开材料与秘密材料在应用接口上分开。本文不讨论业务密钥派生、混合握手、会话恢复或消息加密,也不提供未经审查的自定义协议方案。
- 工具链应支持 crypto/mlkem,部署版本与双方协议约定一致。
- 私钥由接收侧保管,不因调试方便导出到请求体。
- 共享秘密不进入日志、示例输出或监控标签。
- 接口错误直接返回;不在出错时悄悄使用固定值或随机替代值继续。
- 示例两端相等只说明本次内部配对,不等于完成身份认证或互操作认证。
封装密文里是不是业务内容?
不是。它服务于共享秘密的恢复,本文没有传入任何业务明文。
重复封装同一公钥会得到相同结果吗?
不要把封装当作确定性转换。示例依赖标准库随机封装接口,不能通过固定输出字符串判断它是否正确。
为什么没有打印共享秘密供对照?
展示秘密不是理解流程的必要条件。教学比较放在同一进程内;跨端如何认证、确认和使用结果,必须由完整协议负责。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习