具备密码学攻击能力的量子计算机尚未成为日常基础设施,但需要长期保存的数据、软件制品和审计记录不能等到那一天再迁移。Google Cloud KMS 已正式提供 ML-DSA、SLH-DSA 后量子数字签名,并提供 ML-KEM 密钥封装能力。对工程团队而言,真正棘手的问题不只是“换一个算法名称”,而是如何在不把数 GB 文件送进 KMS 或 HSM 的前提下,保持签名协议正确、可验证且不引入新的密钥替换风险。
两类签名算法解决不同问题
Cloud KMS 当前提供的签名算法覆盖 ML-DSA 和 SLH-DSA:
| 算法 | NIST 安全类别 | 可用变体 | 更适合的场景 |
|---|---|---|---|
| SLH-DSA-SHA2-128s | Level 1 | Pure、Pre-hash | 希望通过无状态哈希签名实现纵深防御的系统 |
| ML-DSA-44 | Level 2 | Pure、External-µ | 更看重吞吐量和签名性能的服务 |
| ML-DSA-65 | Level 3 | Pure、External-µ | 在性能和安全强度之间取平衡的通用业务 |
| ML-DSA-87 | Level 5 | Pure、External-µ | 需要长期保护、采用更高安全强度的数据 |
安全类别不能只按“越高越好”选择。更高强度通常意味着更大的密钥、签名或计算成本,实际决策还要考虑验证端兼容性、峰值吞吐、数据保存期限以及监管要求。
SLH-DSA 是基于哈希的无状态签名,可作为算法多样性或纵深防御的一部分。ML-DSA 则更偏向高性能通用签名。迁移前应同时压测签名和验证路径,因为很多系统的验证量远高于签名量,例如软件包仓库、镜像分发和离线归档。
大文件不能直接塞进安全边界
私钥和签名算法内部对象通常具有固定或可预测的大小,待签消息却可能从几十字节增长到数 TB。KMS 与 HSM 的安全边界适合执行密钥运算,却不适合接收和处理海量业务数据。直接上传原始文件会带来带宽、延迟、配额和内存压力。
传统工程做法是在应用侧对文件流式哈希,只把固定长度的摘要交给安全设备。不过,对 ML-DSA 来说,不能把任意 SHA-256(file) 直接当作 External-µ 输入。FIPS 204 Algorithm 7 和相关规范定义了 µ 的构造过程,它还会把公钥与消息代表绑定起来。
这种绑定提供了 non-resignability:攻击者不能把同一个消息代表换到另一把、可能由攻击者控制的公钥下重新解释。也正因为如此,自行拼接“公钥哈希 + 文件哈希”并不等价于标准 External-µ。生产环境应使用 Cloud KMS 支持的变体以及经过验证的密码库实现。
External-µ 的价值在于兼顾两端:
- 应用侧以流式方式处理大文件,避免大载荷进入 KMS。
- KMS 只接收固定大小的消息代表,降低网络和处理延迟。
- 生成的签名仍能交给 pure ML-DSA 验证器处理。
- 消息代表与公钥绑定,保留协议要求的安全属性。
可以这样实践:先建立可审计的摘要流水线
下面的脚本可直接运行,用来演示大文件的流式读取、摘要记录和元数据固化。它生成的是普通 SHA-256 摘要,适合作为迁移前的流水线原型和审计证据,不是可直接发送给 ML-DSA External-µ 接口的 µ。接入生产签名时,要把 sha256 步骤替换成合规库提供的 External-µ 计算函数。
#!/usr/bin/env bash
set -euo pipefail
FILE=${1:?用法: ./prepare-signing-input.sh <file>}
OUT=${2:-signing-input.json}
DIGEST_HEX=$(sha256sum "$FILE" | awk '{print $1}')
SIZE_BYTES=$(wc -c < "$FILE" | tr -d ' ')
BASENAME=$(basename "$FILE")
cat > "$OUT" <<JSON
{
"file": "$BASENAME",
"size_bytes": $SIZE_BYTES,
"digest_algorithm": "SHA-256",
"digest_hex": "$DIGEST_HEX",
"warning": "Audit prototype only; digest_hex is not an ML-DSA external-mu value"
}
JSON
printf 'Wrote %s\n' "$OUT"
例如对一个 4 GiB 测试文件运行:
chmod +x prepare-signing-input.sh
truncate -s 4G payload.bin
./prepare-signing-input.sh payload.bin
cat signing-input.json
真正接入 Cloud KMS API 时,可以沿用下面的请求骨架。运行前需要把资源名称、接口字段和算法枚举替换为当前 Cloud KMS 文档中对应的 ML-DSA External-µ 或 SLH-DSA Pre-hash 定义;不要把上例的普通摘要直接填入请求。
export KMS_KEY_VERSION='projects/PROJECT_ID/locations/LOCATION/keyRings/RING/cryptoKeys/KEY/cryptoKeyVersions/1'
export ACCESS_TOKEN="$(gcloud auth print-access-token)"
curl -sS \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
-X POST \
"https://cloudkms.googleapis.com/v1/${KMS_KEY_VERSION}:asymmetricSign" \
--data @kms-sign-request.json
kms-sign-request.json 应由合规的 External-µ 或 pre-hash 实现生成。把消息代表编码、上下文字符串、算法标识和签名验证测试作为同一个版本化协议管理,避免不同语言的客户端各自解释字段。
迁移时不要只盯着签名端
后量子迁移最容易遗漏的是验证生态。签名服务升级后,旧版 SDK、离线验证器、合作方网关和长期归档工具未必认识新的算法或更大的签名对象。建议先执行双签名或影子验证,在不改变现有信任链的情况下收集兼容性与性能数据。
上线前可以按以下清单检查:
- 盘点需要跨越量子风险窗口保存的数据及其签名有效期。
- 根据合规要求、性能和保存期限选择 ML-DSA-44、65、87 或 SLH-DSA。
- 明确使用 Pure、Pre-hash 还是 External-µ,并写入协议版本。
- 使用标准实现计算 External-µ,禁止自行设计摘要封装格式。
- 对签名大小、请求延迟、KMS 配额、验证吞吐和存储增长做压测。
- 保存算法、密钥版本、上下文和编码信息,确保多年后仍可验证。
- 设计密钥轮换、旧签名验证和算法再次迁移的路径。
- 在切换主链路前运行双签名、影子验证或离线回放。
Cloud KMS 托管密钥和后量子算法可以减少底层实现负担,但不会自动解决协议设计与兼容性问题。稳妥的采用方式是先选择一类长期资产建立端到端试点:从流式处理、KMS 签名、跨语言验证一直跑到归档恢复,再逐步扩大覆盖范围。