4. 密钥生成工程实现
SLH-DSA 的密钥生成并不是“随机生成一对公私钥”这么简单。它的私钥和公钥都由若干固定长度的种子与根节点构成,整个流程可以理解为一次确定性的“种子扩展 + 哈希树构建”。理解这条链路,是后续签名、验签、密钥序列化和硬件实现的基础。
在 FIPS 205 的定义中,SLH-DSA 的私钥包含四个字段:SK.seed、SK.prf、PK.seed、PK.root。其中前两个属于私钥部分,后两个属于公钥部分,但为了签名方便,标准把四个字段一起放进私钥结构。公钥则只包含 PK.seed 和 PK.root。因此,密钥生成的核心任务就是:从一个主种子出发,扩展出 SK.seed 与 SK.prf,再基于 SK.seed 和 PK.seed 构建出哈希树的根 PK.root。
种子扩展:从 n 字节主种子到三个关键种子
SLH-DSA 使用一个安全参数 n,常见取值为 16、24 或 32 字节,分别对应 128、192、256 位安全强度。密钥生成的第一步是获取一个长度为 n 的主种子 seed。这个种子必须来自密码学安全的随机源,例如操作系统的 /dev/urandom、getrandom() 或硬件 TRNG。工程上不建议使用时间戳、进程号等可预测信息拼接。
得到主种子后,使用 SLH-DSA 规定的伪随机函数 PRF 进行扩展。标准中定义了三个带域分隔的派生标签:SK.seed 使用地址类型 SK_SEED,SK.prf 使用 SK_PRF,PK.seed 使用 PK_SEED。它们共享同一个主种子,但通过不同的地址域和索引区分,从而保证三个输出在密码学上相互独立。可以写成:
SK.seed = PRF(seed, SK_SEED, 0)
SK.prf = PRF(seed, SK_PRF, 0)
PK.seed = PRF(seed, PK_SEED, 0)
这里的 PRF 在 SLH-DSA 中通常由 SHA-256 或 SHAKE 等底层哈希构造,具体取决于参数集是“纯 SHA2”还是“SHAKE”变体。工程实现时,应把 SK.seed、SK.prf、PK.seed 都视为长度恰好为 n 的字节串,不要截断或补零,否则会破坏与标准的一致性。
哈希树构建:从 SK.seed 到 PK.root
有了 SK.seed 和 PK.seed 之后,就可以构建 SLH-DSA 的哈希树。SLH-DSA 的树是一棵高度为 h 的完全二叉树,叶子节点数量为 2^h。每个叶子节点对应一个一次性签名公钥(WOTS+ 公钥),其生成过程是:先用 SK.seed 和叶子索引派生出 WOTS+ 私钥,再经过链式哈希得到 WOTS+ 公钥,最后用 PK.seed 和树地址对该公钥做一次压缩,得到叶子节点值。
内部节点的计算则更直接:对左右两个子节点值,加上 PK.seed 和当前节点地址,做一次哈希压缩,得到父节点值。如此自底向上逐层计算,最终得到根节点 PK.root。整个过程是确定性的:只要 SK.seed 和 PK.seed 不变,PK.root 必然相同。这也是 SLH-DSA 能够从私钥重新导出公钥的原因。
工程实现时,树的高度 h 和底层哈希函数由参数集决定。例如 SLH-DSA-SHA2-128s 使用 n=16、h=63,叶子数量为 2^63,实际实现不会真的遍历所有叶子,而是采用惰性计算或缓存策略。对于教学示例,可以取一个极小的参数,例如 n=16、h=3,此时只有 8 个叶子,便于手工验证整棵树的构建过程。
存储格式与序列化
密钥生成完成后,需要把四个字段按标准格式存储。SLH-DSA 的私钥序列化格式为:SK.seed || SK.prf || PK.seed || PK.root,总长度为 4n 字节。公钥序列化格式为:PK.seed || PK.root,总长度为 2n 字节。例如当 n=16 时,私钥为 64 字节,公钥为 32 字节;当 n=32 时,私钥为 128 字节,公钥为 64 字节。
工程实现中,建议把私钥结构体定义为四个固定长度数组,而不是动态长度缓冲区,这样便于内存对齐和常量时间操作。序列化时直接按顺序拼接,反序列化时按 n 切分。需要注意:SK.prf 虽然不参与树构建,但签名时用于生成随机化值,因此必须与 SK.seed 一起安全保存,不能只存 SK.seed。
PK.seed 和 PK.root,或者重新执行一次树根计算来验证 PK.root 是否正确。后者常用于密钥导入时的完整性校验。工程实现要点与常见问题
第一,种子来源必须可靠。如果主种子被攻击者预测,整个密钥体系都会失效。第二,PRF 的域分隔不能省略。有些实现为了省事,直接用 seed 的前 n 字节作为 SK.seed,后 n 字节作为 SK.prf,这不符合标准,会破坏安全证明。第三,树根计算是密钥生成中最耗时的部分,尤其当 h 较大时。实际实现通常采用多线程或 SIMD 加速,但必须保证地址编码和哈希输入顺序与标准一致。
第四,密钥存储建议使用受保护的密钥库或硬件安全模块。如果必须落盘,至少要用口令派生密钥加密,并配合完整性校验。第五,测试时可以用固定种子验证确定性:同一组 seed 和参数集,多次运行应得到完全相同的 PK.root。如果结果不一致,优先检查地址编码、字节序和哈希函数选择是否正确。
SK.seed 和 SK.prf 打印到日志或暴露在调试接口中。它们等价于私钥,一旦泄露,攻击者可以伪造任意签名。最后给出一个可参考的密钥生成检查清单,便于在实现和联调时逐项确认:
| 检查项 | 预期结果 | 判断依据 |
|---|---|---|
| 主种子长度 | 恰好 n 字节 | 与参数集一致 |
| SK.seed / SK.prf / PK.seed | 各 n 字节,互不相同 | 域分隔正确 |
| PK.root 可复现 | 同一 seed 多次生成结果相同 | 确定性验证 |
| 私钥序列化长度 | 4n 字节 | 按标准拼接 |
| 公钥序列化长度 | 2n 字节 | PK.seed || PK.root |
| 私钥存储保护 | 加密或硬件保护 | 无明文泄露 |
掌握这条从主种子到 PK.root 的完整链路后,后续的签名与验签就只是在这棵树上做路径选择和一次性签名运算。密钥生成的质量,直接决定了整个 SLH-DSA 系统的安全边界。