后量子密码迁移:现在要做的不是换算法,而是盘清楚债务
原创 · 约 37 分钟阅读 · 阅读 --
Last updated on

后量子密码迁移:现在要做的不是换算法,而是盘清楚债务

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍在一线做 AI 相关的工程。更完整的技术记录写在微信公众号「字与码」:工作经历、对新工具的看法,以及这些年踩过的坑,会不定期发在那里。若这篇对你有用,欢迎顺手关注。

后量子密码迁移最容易被讲成一个算法故事:RSA 会被 Shor 算法威胁,ECC 也会受影响,NIST 给了 ML-KEM、ML-DSA、SLH-DSA,于是大家应该尽快换新算法。

这个说法没有错,但它太像安全培训课了。真正进到公司系统里,问题完全不是“把 A 算法替换成 B 算法”这么干净。证书在哪里签发,移动 App 包是谁签的,备份密钥放在哪个 KMS 里,合作伙伴接口验签有没有文档,旧 SDK 还能不能升级,这些细碎问题才会决定迁移能不能做下去。

下面我用一个虚构但很贴近现实的 SaaS 公司做主线。公司叫 Northwind Notes,一家给企业做协作文档和审批流的 B2B SaaS。它没有国家级机密,也没有神秘硬件;它只是有公网 TLS、移动 App、每天备份、合作伙伴开放 API、一些历史包袱。安全负责人给了团队一个目标:90 天内做完后量子迁移试点,不追求全面替换,但要把账盘清楚,并跑通一条可回滚的真实链路。

后量子密码迁移路线图

第一天的误会:我们不是已经全站 HTTPS 了吗

Northwind Notes 的第一场会开得很快。业务团队听到“后量子密码迁移”,第一反应是:公网服务都在 HTTPS 后面,证书也是云厂商托管,应该找网关团队看一眼就行。

网关团队也确实很快拉出了一张清单:三个公网域名、两个区域的负载均衡、一个 CDN 配置、一个内部 mTLS 网格。清单看起来很整齐,直到移动端负责人问了一句:“App 安装包签名算不算?”备份负责人又问:“备份加密用的 RSA 包装密钥算不算?”生态合作团队补了一刀:“我们还有几个伙伴接口是 RSA-SHA256 签名,合同里写了签名串格式。”

这才是迁移的真实开头。PQC 不是 TLS 专项治理,而是密码资产治理。密码资产不只在安全团队手里,也不只在基础设施里。它藏在发布流水线、移动端证书、归档任务、合作伙伴 SDK、老的批处理脚本和一堆“当年赶时间写的签名工具”里。

这家公司后来把 90 天目标改成了三句话:

目标不是为了真正要交付
盘点密码资产写一份漂亮报告找出长期保密、升级慢、责任不清的资产
做混合试点证明新算法很先进验证兼容性、观测、回滚和供应商边界
建密码敏捷性一次性替换所有算法以后算法变化时不用再全公司挖代码

这个转向很重要。后量子迁移如果一开始就喊“全面升级”,很容易变成大而虚的安全项目。反过来,如果先承认自己不知道哪里用了密码,项目就会落到可执行的清单、接口和流程上。

资产清单不是 CMDB,它要能让人排序

第一周,Northwind Notes 没有买平台,也没有先引入新库。安全团队只是发了一份很朴素的表格,让每个系统负责人填。表格字段不是“系统名、负责人、备注”这种行政口径,而是围绕迁移决策设计的。

字段示例用来回答的问题
资产编号CRYPTO-API-017后续测试、风险和工单能不能指向同一件事
场景合作伙伴订单回调验签这是传输加密、静态加密,还是数字签名
算法与参数RSA-2048 / SHA-256 / PKCS#1 v1.5受量子风险影响多大,是否还有传统弱点
数据保密期7 年是否存在 harvest now, decrypt later 风险
密钥或证书生命周期2 年轮换下一次自然窗口在哪里
依赖方8 个合作伙伴、3 个内部服务谁会被迁移影响
所在代码或配置api-signature-service / v2 signer能不能找到真实实现
供应商约束HSM 固件暂不支持 PQC迁移卡点是不是外部能力
回滚办法feature flag 切回 RSA试点失败时是否能退
当前观测只有 401 计数,无算法维度上线后能不能定位失败

表格很快暴露出几个问题。公网 TLS 倒是清楚,因为证书管理集中;移动 App 签名只知道证书到期日,不知道老设备验签策略;备份加密用了云 KMS,但 KMS 内部如何做密钥封装,团队没看过文档;合作伙伴 RSA 签名最麻烦,签名代码散在一个老服务和两个 SDK 里,甚至还有伙伴按字段排序的私有差异。

第一周结束时,他们得到的不是“迁移完成 12%”这种数字,而是一张可以排序的风险图。

资产当前做法主要风险90 天动作
公网 API TLS云 LB,ECDHE + ECDSA/RSA 证书长期敏感请求可能被采集;中间设备兼容未知内部入口做混合 KEM 试点,不直接动全站
移动 App 签名Android/iOS 平台签名 + 内部包签名旧版本验证链长期存在;离线验签周期长补齐签名链文档,先做双签名演练
备份加密AES 数据密钥,RSA 包装历史归档密钥备份保密期 7 到 10 年,典型先收集后解密风险新备份改成可插拔 envelope,旧备份建立重加密计划
合作伙伴 API 签名RSA-SHA256,请求级签名外部改造慢,协议写死,验签失败难排查设计 v3 签名协议,先支持双验签
内部 mTLSservice mesh 管理客户端版本相对统一,但流量大选低风险内部链路做握手观测试点

这张表比算法选型更早出现,也更有价值。它让会议从“是不是要换 ML-KEM”变成“哪条链路最值得在 90 天内跑通”。

不同密码用途,迁移节奏完全不同

Northwind Notes 后来把资产分成四类:密钥交换、传输层证书、静态数据加密、数字签名。它们都和后量子有关,但迁移难点不一样。

TLS 和 mTLS 主要看协议栈、客户端、网关和中间设备。试点可以走混合密钥交换,失败可以按流量切回旧配置。它有风险,但至少是在线系统,可观测、可灰度。

备份加密看起来更简单,因为没有浏览器兼容问题。实际难点在保密期。备份是冷的,但价值很长。攻击者今天拿到密文,几年后有能力解密,业务损失仍然成立。这个场景比一些公网短会话更应该早处理。

移动 App 签名和合作伙伴签名又是另一种麻烦。签名的验证方很多,验证时间可能很久以后发生。一个 App 包、一个审计归档、一个合同回调日志,可能几年后还要被验证。签名迁移不能只看“今天能不能验”,还要看“未来如何证明当时的签名有效”。这会引入双签名、时间戳、旧证书保留、验证器版本管理。

团队一开始想把 90 天试点放在公网 TLS。后来放弃了。原因很现实:公网客户端不可控,云厂商对混合 KEM 的生产支持还在排期,直接做容易把项目变成供应商等待。最后他们选了两条线并行:

试点线为什么选它为什么不是终局
内部备份加密 envelope 重构数据保密期长,链路可控,能真实降低长期风险只覆盖新备份,旧备份还要分批处理
合作伙伴签名 v3 双验签代表外部协调难点,能提前暴露协议治理问题90 天内只接入两个沙盒伙伴

这个选择比“挑一个最好展示的新算法”更稳。一个试点验证静态加密的密码敏捷性,一个试点验证签名迁移的外部兼容性。TLS 仍然在路线图里,但不再硬塞进 90 天。

备份线:把算法从代码里拔出来

Northwind Notes 的备份系统历史不复杂:每天从数据库和对象存储导出快照,用随机 AES-256-GCM 数据密钥加密,数据密钥再用一个 RSA 公钥包装,最后把密文、包装后的数据密钥和元数据一起写到归档桶。

老格式类似这样:

{
  "version": "backup.v1",
  "data_cipher": "AES-256-GCM",
  "key_wrap": "RSA-OAEP-SHA256",
  "wrapped_data_key": "base64...",
  "nonce": "base64...",
  "created_at": "2026-01-04T03:10:12Z"
}

这个格式的问题不在 AES,而在 key wrap 写死了 RSA。更麻烦的是恢复工具也写死了同一个假设:看到 backup.v1 就按 RSA-OAEP 解包。要做 PQC 或混合封装,第一步不是马上换算法,而是把 envelope 版本化。

第二版元数据改成了这样:

{
  "version": "backup.v2",
  "data_cipher": "AES-256-GCM",
  "key_envelopes": [
    {
      "id": "rsa-2026-q1",
      "type": "rsa-oaep-sha256",
      "recipient": "backup-rsa-prod-01",
      "wrapped_data_key": "base64..."
    },
    {
      "id": "mlkem-2026-q1",
      "type": "ml-kem-768-hybrid-x25519",
      "recipient": "backup-pqc-pilot-01",
      "encapsulated_key": "base64...",
      "wrapped_data_key": "base64..."
    }
  ],
  "nonce": "base64...",
  "aad": {
    "tenant_region": "us-east-1",
    "retention_class": "seven_years"
  },
  "created_at": "2026-03-18T03:10:12Z"
}

这里的关键不是某个字段名,而是设计原则:数据加密层继续用对称加密;密钥封装层允许多个 envelope 并存;恢复工具按能力选择 envelope;每个 envelope 都有版本、接收者和审计信息。这样即使第一版 PQC 方案要回滚,也不会破坏备份主体。

对应的配置没有放在业务代码里,而是进入了一个受控配置:

backup_crypto_profile:
  name: seven-year-retention-pilot
  applies_to:
    retention_class: seven_years
    regions:
      - us-east-1
  data_cipher: AES-256-GCM
  envelopes:
    - id: rsa-2026-q1
      type: rsa-oaep-sha256
      required_for_restore: true
      recipient_key_ref: kms://backup/rsa/prod-01
    - id: pqc-hybrid-2026-q1
      type: ml-kem-768-hybrid-x25519
      required_for_restore: false
      recipient_key_ref: kms://backup/pqc/pilot-01
  rollout:
    mode: shadow
    tenant_percent: 5
    stop_on_restore_failure: true

第一阶段 mode: shadow 的意思是:写入新 envelope,但恢复路径仍以 RSA envelope 为准,同时在后台定期用 PQC envelope 做恢复演练。这样可以真实产生数据,又不会把生产恢复能力押在新路径上。

这个设计也给后续留下空间。等运行足够久,required_for_restore 可以调整;等供应商和审计都准备好,RSA envelope 可以从“主路径”退成“兼容路径”;旧备份也可以按保密期分批重加密,而不是一次性做一个危险的大迁移。

备份线的第一次失败:恢复工具没错,值班手册错了

第 32 天,团队做第一次恢复演练。加密和解密单元测试都过了,影子恢复也能跑。但演练还是失败了,原因很不起眼:值班手册里写的是“复制 wrapped_data_key 到恢复命令”,而新格式里有多个 envelope。值班同学按旧手册拿了 RSA envelope 的字段,却在新恢复工具里指定了 PQC profile,工具报了一个很底层的 “recipient mismatch”。

这类失败很典型。密码迁移经常不是算法实现失败,而是周边操作假设失败。工具的错误信息、手册、审计字段、监控面板都默认“只有一个密钥包装方式”。当格式变成多 envelope,这些默认假设会一起浮出来。

他们做了三个小改动:

backupctl restore \
  --archive s3://backup-archive/2026/03/18/tenant-1042.snapshot \
  --crypto-profile seven-year-retention-pilot \
  --envelope auto \
  --dry-run

--envelope auto 会列出可用 envelope,并解释选择原因。--dry-run 会验证元数据和密钥访问权限,但不真正写回数据库。错误信息也从底层异常改成了面向值班人员的话:

cannot use envelope pqc-hybrid-2026-q1:
  plugin can read archive metadata, but KMS recipient backup-pqc-pilot-01 is not granted to this restore role.
suggestion:
  run with --envelope rsa-2026-q1, or request restore role backup-pqc-restore-pilot.

这不是密码学突破,但它决定试点能不能进生产。安全项目经常低估这些人机接口。真正的回滚和恢复不是写在架构图里的箭头,而是半夜值班的人能不能按手册做对。

合作伙伴签名线:先做双验签,不急着让别人改

合作伙伴 API 是另一个世界。Northwind Notes 有一个开放接口,伙伴推送审批状态变更。旧协议大概长这样:

POST /partner/v2/callback HTTP/1.1
X-Partner-Id: acme-sandbox
X-Timestamp: 1771272000
X-Signature-Alg: RSA-SHA256
X-Signature: base64...
Content-Type: application/json

签名串是:

method + "\n" +
path + "\n" +
timestamp + "\n" +
sha256(body)

这个协议有几个老问题:算法字段只是字符串,没有版本协商;伙伴公钥更新靠人工邮件;错误日志只记录“验签失败”;SDK 里有伙伴自定义排序逻辑。PQC 迁移只是把这些问题照亮了。

新协议没有要求伙伴马上换后量子签名。团队先设计了 v3 manifest,让伙伴声明自己支持的签名能力,宿主支持双验签和灰度拒绝。

{
  "partner_id": "partner-sandbox-17",
  "protocol": "northwind.partner-signature.v3",
  "valid_from": "2026-04-01T00:00:00Z",
  "keys": [
    {
      "kid": "rsa-2026-01",
      "alg": "rsa-pss-sha256",
      "use": "verify",
      "public_key": "pem-or-jwk-ref",
      "expires_at": "2026-12-31T23:59:59Z"
    },
    {
      "kid": "mldsa-pilot-01",
      "alg": "ml-dsa-65",
      "use": "verify",
      "public_key": "jwk-ref",
      "expires_at": "2026-09-30T23:59:59Z",
      "pilot": true
    }
  ],
  "policy": {
    "accept": "rsa_or_pqc",
    "require_timestamp_skew_seconds": 300,
    "log_signature_material": false
  }
}

请求头也改成支持多个签名:

X-Partner-Id: partner-sandbox-17
X-Signature-Input: method path timestamp body-sha256
X-Signature-RSA: kid=rsa-2026-01; sig=base64...
X-Signature-PQC: kid=mldsa-pilot-01; sig=base64...

试点期策略是 rsa_or_pqc。只要 RSA 通过,请求仍然被接受;PQC 验签结果进入日志和看板,但不影响业务。等两个沙盒伙伴连续 30 天通过率达标,再切一个低风险接口到 rsa_and_pqc,要求双签名都通过。最终目标才是某些新接口只接受 PQC 或混合签名。

这里有个容易被忽略的点:双签名不是简单加一个字段。签名输入必须完全一致,错误码要能说明是哪一个签名失败,重放保护不能因为两个签名出现歧义,SDK 要能生成测试向量。否则伙伴只会看到 401,然后双方开始在群里贴日志。

合作伙伴线的失败样本:验签失败不是一种错误

第 51 天,一个沙盒伙伴接入 v3,PQC 签名一直失败。最开始大家怀疑是算法库版本不一致,后来发现是签名输入里的 path 规范化不同。伙伴 SDK 签的是 /callback,网关验的是 /partner/v3/callback。旧 RSA SDK 里也有这个问题,只是旧服务在验签前做了一个隐式 rewrite,没人写进文档。

这件事让团队把验签错误拆成了更细的类型:

错误码含义是否计入算法失败
signature.input_mismatch双方签名输入不一致否,协议接入问题
signature.key_not_found找不到 kid 对应公钥否,配置或轮换问题
signature.unsupported_alg宿主不支持该算法是,能力协商问题
signature.verify_failed输入和 key 都匹配,但验签失败是,签名实现或数据篡改
signature.clock_skew时间戳超出窗口否,重放保护问题

他们还生成了一组公开测试向量,放进 SDK 仓库和伙伴文档:

{
  "case": "v3-path-normalization",
  "method": "POST",
  "path": "/partner/v3/callback",
  "timestamp": "1771272000",
  "body_sha256": "d4735e3a265e16eee03f59718b9b5d03...",
  "signature_input": "POST\n/partner/v3/callback\n1771272000\nd4735e3a...",
  "expected": {
    "rsa_pss_sha256": "base64...",
    "ml_dsa_65": "base64..."
  }
}

测试向量不包含真实伙伴名、真实密钥、真实请求体,只保留协议行为。它的作用是把“你们库是不是不兼容”的争论,变成双方都能本地复现的输入输出。

TLS 没有消失,只是被放回正确位置

这篇文章不是说 TLS 不重要。Northwind Notes 也做了 TLS 盘点,只是没有把它当成 90 天唯一战场。

TLS 盘点包括这些项:

项目检查点
公网域名证书算法、证书链、有效期、CDN 和 LB 支持路线
内部 mTLSservice mesh 版本、sidecar 更新节奏、失败指标
客户端分布浏览器、移动 App、老 SDK、企业代理
中间设备WAF、API 网关、企业代理是否会丢弃未知扩展
观测握手失败是否能按协议、cipher、客户端版本聚合

他们在第 60 天选了一条内部链路做预研:从文档服务到审计归档服务的 mTLS。链路流量稳定,客户端都是受控 sidecar,数据有长期审计价值,失败可以切回旧目标。

试点配置没有直接上“全量 PQC”,而是提供了明确开关:

mesh_tls_policy:
  service: audit-archive
  client_selector:
    namespace: docs
  key_exchange:
    mode: hybrid
    classical: x25519
    post_quantum: ml-kem-768
  rollout:
    percent: 10
    fallback: classical_only
    abort_if:
      handshake_error_rate_gt: 0.2%
      p95_handshake_ms_increase_gt: 15
  telemetry:
    label_algorithm: true
    label_client_version: true

真正有价值的是 telemetry。以前握手失败就是一条 TLS error,现在至少能看到是哪个 sidecar 版本、哪种协商模式、哪个错误族。没有这个维度,混合模式上线后出了问题,只能全局回滚。

测试办法:别只测“能不能加解密”

90 天试点里,Northwind Notes 把测试分成四层。第一层才是算法正确性,后面三层更接近工程现实。

测试层备份线样例签名线样例TLS 线样例
正确性同一备份用 RSA 和 PQC envelope 都能恢复测试向量验签通过sidecar 能完成混合握手
兼容性旧恢复工具能拒绝新格式并给清楚错误v2 伙伴不受 v3 manifest 影响旧 sidecar 自动走 classical
故障注入KMS PQC key 不可用时能用 RSA 恢复kid 过期、path 不一致、时钟偏移中间代理丢扩展、握手超时
操作演练值班按手册恢复一份样本伙伴按文档本地生成签名SRE 按指标触发回滚

他们没有把测试写成“全部自动化才算完成”。有些测试必须自动化,比如测试向量和恢复 dry-run;有些测试反而要人参与,比如值班手册演练。密码迁移会改变操作流程,人也在系统里。

每周例会看四个指标:

指标目标
高优先级密码资产 owner 覆盖率100%
新备份 v2 envelope 写入成功率99.99% 以上
PQC shadow restore 成功率连续 14 天 100%
v3 签名错误可分类比例95% 以上

这里没有“已经迁移百分之多少”的大数字,因为 90 天试点不该假装完成全公司迁移。它要证明的是:资产能被看见,失败能被解释,路径能被回滚。

发布和回滚:后量子迁移要允许“不成功但不事故”

发布计划写得很保守。

备份线分四步:

阶段行为回滚
0只读取旧 v1,不写新格式无需回滚
15% 租户写 v2,RSA envelope 仍为主恢复路径停止写 v2,新备份回到 v1
2全量写 v2,后台 shadow restore PQC envelope禁用 PQC envelope,保留 RSA
3指定租户恢复演练优先用 PQC envelope恢复命令切回 RSA envelope

合作伙伴签名线也分四步:

阶段行为回滚
0v3 manifest 只在沙盒注册删除 manifest,不影响 v2
1生产接受 v2;v3 双签只记录关闭 v3 验签 worker
2两个低风险伙伴启用 rsa_or_pqcpartner policy 改回 rsa_only
3单个低风险接口启用 rsa_and_pqcpolicy 回退并保留失败样本

这个发布计划的核心是:每一步都能回退到已知安全的传统路径,同时不丢失观测数据。回滚不是承认失败,而是试点设计的一部分。没有回滚的密码迁移,会让所有人倾向于拖延,因为没人愿意背一个“全站验签失败”的锅。

90 天结束时,真正交付了什么

第 90 天,Northwind Notes 没有宣布“我们已经完成后量子迁移”。这反而是一个成熟的结论。他们交付的是一组更朴素但可持续的东西:

交付物价值
72 条密码资产清单,其中 28 条高优先级后续预算和排期有了依据
备份 v2 envelope 格式和恢复工具长期保密数据有了迁移入口
合作伙伴 v3 签名协议、manifest、SDK 测试向量外部协调从口头改成契约
mTLS 混合握手观测字段以后扩大 TLS 试点时能看懂失败
供应商问题清单HSM、KMS、CDN、移动平台支持节奏被记录
发布和回滚模板其他系统能复用同一套变更方式

也有没完成的部分。旧备份没有全部重加密;公网 TLS 没有启用 PQC;移动 App 签名还停留在双签名演练;合作伙伴只接了两个沙盒。这些都不丢人。90 天要解决的是“不知道怎么开始”,不是把未来几年的工作一口吞下。

我更看重他们留下来的几个工程习惯:

习惯为什么重要
算法不写死在业务代码里以后标准、库、供应商变化时可切换
密钥和证书有 owner没有人认领的密码资产最危险
新协议先支持双轨外部生态迁移需要缓冲
错误要能分类只有“失败”两个字无法推动合作
值班手册和工具一起改迁移会影响真实操作,不只是代码

这就是我理解的密码敏捷性。它不是一个框架名,也不是某个厂商产品。它是一种组织能力:知道自己在哪里用了密码,知道谁负责,知道怎么试,知道怎么退。

给自己的一个小检查表

如果你现在也要启动类似项目,不妨先问这些问题。它们比“我们什么时候全面换成 PQC”更容易得到真实答案。

问题如果答不上来,说明什么
哪些数据今天被抓走,五年后仍然有价值?风险排序还没建立
哪些签名需要几年后仍可验证?签名迁移没有考虑长期证据
哪些密码能力依赖供应商?时间表可能不是内部说了算
哪些算法写死在业务代码里?迁移会变成逐仓库改造
哪些失败现在只能看到 401 或 TLS error?上线后无法定位兼容问题
有没有一条链路可以真实试点并快速回滚?项目可能只能停留在文档

后量子密码当然有算法问题,但公司里的后量子迁移首先是资产问题、协议问题、发布问题和协作问题。现在最该做的不是到处替换 RSA,也不是等量子计算机真的可用才开会,而是把密码债务摊开,挑一条真实链路跑起来。

等那一天真的需要大规模切换时,准备好的团队不会临时翻代码找签名函数。他们会打开清单,调整 profile,灰度发布,看指标,必要时回滚。那才是这件事最值得提前建设的部分。