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

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各端本地,不写日志
接收侧私钥与公开封装材料的分组结构
图1:原创静态结构图。接收侧秘密与公开材料分开,关系线仅标明持有、导出与封装依赖,不表示通信时序或运行结果。

用函数封装一次密钥建立

下面是同进程教学示例,用于理解两侧职责,没有网络层、身份认证或业务数据加密。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 只比较本地两端结果。应用协议的认证与确认不在这几个函数内;实际系统应使用经过审查的认证协议,不应把这个示例直接当作生产握手替代品。

三个封装函数与输入错误及认证边界的关系
图2:原创静态契约图。原语调用和协议边界分别分组,强调错误处理与身份认证是不同职责;不是测试输出或协议时序图。

迁移时先固定参数与职责

迁移的第一步不是替换所有旧密码接口,而是确定两端都支持同一参数集和编码,并让公开材料与秘密材料在应用接口上分开。本文不讨论业务密钥派生、混合握手、会话恢复或消息加密,也不提供未经审查的自定义协议方案。

  • 工具链应支持 crypto/mlkem,部署版本与双方协议约定一致。
  • 私钥由接收侧保管,不因调试方便导出到请求体。
  • 共享秘密不进入日志、示例输出或监控标签。
  • 接口错误直接返回;不在出错时悄悄使用固定值或随机替代值继续。
  • 示例两端相等只说明本次内部配对,不等于完成身份认证或互操作认证。

封装密文里是不是业务内容?

不是。它服务于共享秘密的恢复,本文没有传入任何业务明文。

重复封装同一公钥会得到相同结果吗?

不要把封装当作确定性转换。示例依赖标准库随机封装接口,不能通过固定输出字符串判断它是否正确。

为什么没有打印共享秘密供对照?

展示秘密不是理解流程的必要条件。教学比较放在同一进程内;跨端如何认证、确认和使用结果,必须由完整协议负责。

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