3. 密钥生成原理:从随机种子到签名密钥对
密钥生成,是整个 Ed25519 签名体系的起点。很多人觉得这步就是调个库函数完事,但我在嵌入式移植中踩过不少坑——随机数生成器不够安全、种子管理不当、公钥推导过程理解有偏差,这些都会让后续的签名验证形同虚设。今天咱们就把这层窗户纸捅破。
3.1 私钥生成机制:种子才是真正的秘密
Ed25519 的私钥,其实不是直接生成的。它背后有一个更核心的东西——种子(seed)。
标准流程是这样的:
- 系统生成一个 256 位(32 字节)的安全随机数,这就是种子。
- 对种子做 SHA-512 哈希,得到 64 字节的哈希值。
- 取哈希值的前 32 字节,经过“削剪”处理(后面细说),得到私钥
sk。 - 取哈希值的后 32 字节,作为推导公钥时的“非对称密钥前缀”。
说白了,种子就是私钥的私钥。你只要保管好种子,随时可以恢复出完整的密钥对。我在一个 IoT 项目中,设备出厂时只烧录种子,私钥和公钥都在首次上电时现场推导——这样即使固件被 dump,攻击者也拿不到明文私钥。
核心结论:Ed25519 的“私钥”是可推导的,真正的秘密是那 32 字节的种子。
3.2 公钥推导过程:点乘运算的艺术
有了私钥,公钥怎么来?一句话:私钥乘以椭圆曲线的基点。
具体步骤:
- 私钥
sk是一个 32 字节的标量(经过削剪后的小端整数)。 - 基点
B是 Ed25519 曲线上的固定点,坐标是公开的。 - 公钥
pk = sk * B,这是椭圆曲线上的标量乘法(点乘)。
这里有个细节——削剪(clamping)。私钥的前 32 字节不能直接用,得做三步操作:
- 将最低 3 位清零(确保是 8 的倍数,抵抗小群攻击)。
- 将最高位清零(确保标量小于曲线阶)。
- 将次高位设为 1(防止非素数阶子群攻击)。
嗯,这些设计都是有密码学依据的。我曾经在移植时忘了做削剪,结果签名验证时好时坏,查了两天才发现是这里出了问题。
// 伪代码:Ed25519 公钥推导
func derive_public_key(seed [32]byte) [32]byte {
hash := sha512(seed)
// 前 32 字节做削剪
sk := clamp(hash[0:32])
// 后 32 字节是前缀
prefix := hash[32:64]
// 点乘:pk = sk * B
pk := scalar_mult(sk, BASE_POINT)
return pk
}
3.3 种子与密钥对的关系:一棵树上的果实
种子、私钥、公钥三者的关系,可以用一张图说清楚。
我的经验:在嵌入式设备中,我习惯把种子存储在安全元件(SE)或 eFuse 中,私钥和公钥只在 RAM 中临时存在。这样即使设备被物理破解,攻击者也拿不到持久化的私钥。
3.4 安全随机数生成:密钥安全的基石
种子是随机生成的。如果随机数生成器(RNG)不够安全,整个密码体系就是纸糊的。
Ed25519 标准要求使用密码学安全伪随机数生成器(CSPRNG)。常见的选项:
| 平台 | 推荐 API | 说明 |
|---|---|---|
| Linux | /dev/urandom 或 getrandom() |
内核维护的熵池,足够安全 |
| MCU (ARM Cortex) | 硬件 RNG 外设 + 软件去偏 | 需要验证硬件 RNG 是否符合 SP 800-90B |
| ESP32 | esp_random() |
基于硬件噪声,但建议混合软件熵 |
| STM32 | RNG 外设 + 自检 | 注意某些型号 RNG 有已知缺陷 |
⚠️ 避坑指南:我曾经在一个项目中使用 MCU 内置的伪随机数生成器(PRNG)来生成种子,结果发现每次上电后的前几个随机数竟然是一样的——因为 PRNG 的种子是固定的。从那以后,我坚持使用硬件 RNG 或混合多路熵源。
在嵌入式环境中,我推荐一种“混合熵”策略:
- 从硬件 RNG 读取 32 字节。
- 混入芯片唯一 ID(如 MCU 的 UID)。
- 混入一个高精度定时器的低几位(如 SysTick 的纳秒级抖动)。
- 对混合结果做一次 SHA-256,得到最终种子。
这样做的好处是:即使硬件 RNG 有缺陷,其他熵源也能弥补。说白了,就是不要把鸡蛋放在一个篮子里。
3.5 密钥对的一致性验证
生成完密钥对,怎么确认它们是一对?我教你一个简单的自检方法:
// 自检:验证密钥对一致性
func verify_keypair(seed [32]byte) bool {
pk1 := derive_public_key(seed)
// 重新推导一次,看公钥是否一致
pk2 := derive_public_key(seed)
return pk1 == pk2
}
如果返回 false,说明推导过程中有随机性介入——那你的实现就有 bug。正常的 Ed25519 实现,给定同一个种子,永远得到同一个公钥。
记住:确定性推导是 Ed25519 的重要特性。它意味着你可以从种子恢复整个密钥对,不需要额外存储私钥和公钥。
嗯,密钥生成原理就讲到这里。种子是根,私钥是干,公钥是果——理解了这三者的关系,后面的签名和验证就水到渠成了。