密钥生成策略:随机数生成器、熵源、生成算法选择

密钥生成的本质,就是让系统“猜不出”的随机比特流。你可能觉得这很简单——不就是调个 rand() 吗?

我早年接手过一个硬件钱包项目,团队直接用 libc 的 rand() 生成 ECDSA 私钥。结果呢?测试时发现每次设备上电后生成的密钥序列都一模一样。嗯,这就是典型的“伪随机数当真随机用”的翻车案例。

这一章我们就把密钥生成策略拆成三块:随机数生成器、熵源、生成算法。这三者的关系,你可以想象一个流水线:熵源是原材料,生成器是加工车间,算法是质检标准。任何一个环节出问题,最终密钥都是脆弱的。

密钥生成流水线 熵源 硬件噪声/物理熵 TRNG 真随机数发生器 PRNG 伪随机数发生器 生成算法 HMAC_DRBG/CTR_DRBG 种子/熵定期补充 密钥材料 核心原则:熵源不可预测 + 生成器状态不可观测 = 安全密钥

随机数生成器的类型与选择

随机数生成器分两大类:真随机数生成器 (TRNG) 和 伪随机数生成器 (PRNG)。

TRNG 依赖物理过程——比如电阻热噪声、时钟抖动、量子效应。它输出的是“真随机”,但速度慢,而且硬件成本高。我在做金融级 HSM 项目时,芯片里集成的 TRNG 模块每秒钟只能产出几十 KB 的随机数,完全不够对称密钥批量生成用的。

PRNG 呢?它用确定性的算法(比如 HMAC_DRBG、CTR_DRBG)从一段种子密钥扩展出无限长的伪随机序列。速度快,但一旦种子泄漏或者状态被逆向,整个序列就废了。

特性 TRNG PRNG
熵来源 物理噪声 种子(来自 TRNG 或前一轮状态)
输出速度 慢(~KB/s 级别) 快(~MB/s 甚至 GB/s)
可预测性 理论上不可预测 种子未知时不可预测
典型场景 种子生成、一次性密钥、CA 根密钥 会话密钥、对称密钥批量生成

实用原则:永远不要用 TRNG 直接生成大量对称密钥。你应该用 TRNG 产生一个高熵种子(比如 256 位),然后交给 PRNG 扩展。很多 SDK 内部就是这么做的——NIST SP 800-90A 标准化的 DRBG 机制就是最佳实践。

熵源的重要性与评估

说白了,熵源就是密钥生成的“米”。米不好,再好的厨子也做不出好饭。

我见过最离谱的案例:某物联网芯片为了省成本,直接用芯片内部的一个自由运行计数器的低几位作为熵源。结果因为每次启动时上电时序几乎一致,计数器初值也差不多,生成的密钥只有两种变体。嗯,那批设备基本等于没有加密。

评估熵源质量,我习惯看三个指标:

  • 熵率(bits/bit):每个比特携带多少真随机信息。理想值接近 1.0。
  • 输出不可预测性:即使攻击者知道之前的所有输出,也无法推断下一比特。
  • 稳定性:温度、电压、老化等环境下,熵率不会剧烈下降。

如果你在 Linux 上开发,/dev/random 和 /dev/urandom 就是典型的熵源接口。前者会阻塞等待内核熵池充足,后者永不阻塞但可能用残留熵。我个人的建议:密钥种子务必从 /dev/random 获取,非关键随机数可以用 /dev/urandom。

小技巧:在容器或虚拟化环境中,内核熵池经常被耗尽。我曾经遇到 K8s 中 Pod 启动后 /dev/random 一直阻塞的坑。解决方案是用 haveged 或 rng-tools 注入硬件熵,或者从宿主机的 /dev/urandom 读取种子。

生成算法与标准化

算法选择这块,大部分现代加密 SDK 已经帮你封装好了,但你要理解它背后的逻辑。

主流标准是 NIST SP 800-90A 定义的三种 DRBG:

  • HMAC_DRBG:基于 HMAC,安全性强但计算开销略大。适合对安全极度敏感的场景。
  • CTR_DRBG:基于分组密码 (如 AES-256),速度快。大多数 SDK 的默认选项。
  • Hash_DRBG:基于哈希函数,现在用得少了,因为它对状态管理的容错性不如前两者。

另外,RFC 6979 定义了一种确定性 ECDSA 签名 与密钥生成无关 吗?等等——这里容易混淆。RFC 6979 解决的是签名时 k 值随机性问题,不是密钥生成。但它的思想可以借鉴:用 HMAC 结合私钥和消息摘要产生确定性随机数,避免 RNG 故障。

// OpenSSL 生成 ECC 密钥对示例(底层使用了 DRBG):
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1);
EVP_PKEY *pkey = NULL;
EVP_PKEY_keygen(ctx, &pkey);
// 密钥自动由 SDK 内部 DRBG 生成,种子来自 OS 熵源

踩坑回忆:我曾经在某国产芯片上调用 OpenSSL 生成的 RSA 密钥,发现每次生成的素数因子竟然相同——因为该芯片的硬件 TRNG 模块被关闭了,但 RAND_bytes() 并未报错。从那以后,我强制要求所有 SDK 密钥生成后做一次已知密钥测试:RAND_status() 加上检验函数。

密钥生成参数对比

算法场景 推荐生成器 最少安全强度 常用熵源
AES-256 对称密钥 CTR_DRBG (AES-256) 256 位 /dev/random 或 TRNG 种子
RSA-4096 私钥 HMAC_DRBG (SHA-512) 4096 位 硬件 TRNG + /dev/random
ECDSA P-384 CTR_DRBG (AES-256) 384 位 /dev/urandom + 额外熵混合
一次性会话密钥 PRNG (快速模式) 128 位 /dev/urandom

核心总结:密钥生成不是“调一个 API”那么简单。你需要想清楚三件事——熵源可信吗?生成算法经过 NIST 认证吗?种子更新机制到位了吗?把这三根柱子立稳,密钥的安全根基才算夯实。