3. 密钥生成策略:TRNG vs PRNG 与 KDF 的应用

密钥生成,是整个烧录流程的起点。起点要是歪了,后面做得再漂亮也没用。我见过不少产品,加密算法选得挺强,结果密钥生成环节出了纰漏,整个安全体系形同虚设。今天咱们就聊聊这个核心问题:到底该怎么生成一把靠谱的密钥?

3.1 真随机数生成器(TRNG)

TRNG,说白了就是从物理世界中提取随机性。比如芯片内部的热噪声、振荡器的抖动、甚至某些传感器的噪声。这些源头的不可预测性,理论上可以对抗任何攻击者。

我个人习惯把 TRNG 比作「大自然的骰子」。你没法预测它下一次会扔出几点,因为它的随机性来源于物理过程,而不是算法。

TRNG 的核心优势:

  • 熵源来自物理过程,理论上不可预测
  • 适合生成高安全等级的主密钥(如根密钥、设备私钥)
  • 能抵抗种子攻击(因为没有种子)

但 TRNG 也不是万能的。我在项目中遇到过一个问题:某款芯片的 TRNG 在低温环境下输出质量明显下降。嗯,这里要注意——TRNG 的输出需要经过后处理,比如通过 NIST SP 800-90B 的熵源健康测试,否则直接拿来做密钥可能会出事。

避坑指南:我曾经在一个 IoT 项目中,发现 TRNG 输出的比特流存在 0/1 偏斜。后来加了冯·诺依曼去偏算法才解决。记住:TRNG 原始输出 ≠ 安全密钥,必须经过后处理和健康检测。

3.2 伪随机数生成器(PRNG)

PRNG 呢,则是通过确定性算法从种子生成随机数。你想想看,只要种子相同,PRNG 的输出序列就完全一样。这既是它的优点(可复现),也是它的致命弱点(种子泄露就全完了)。

常见的 PRNG 算法包括:

算法 输出长度 安全等级 典型应用
CTR_DRBG 可变 高(AES-256 支撑) FIPS 140-3 认证设备
Hash_DRBG 可变 高(SHA-256 支撑) 嵌入式安全芯片
HMAC_DRBG 可变 高(HMAC-SHA 支撑) 高安全场景
LCG(线性同余) 固定 低(可预测) 非安全场景(如测试)

我建议在密钥烧录场景中,优先选择 NIST SP 800-90A 标准化的 DRBG(确定性随机比特生成器)。CTR_DRBG 是我个人用得最多的,因为它基于 AES 硬件加速器,性能好,而且安全强度够。

实用技巧:PRNG 的种子必须来自 TRNG。换句话说,TRNG 负责「点火」,PRNG 负责「持续燃烧」。两者配合使用,才是工程上的最优解。

3.3 TRNG vs PRNG:怎么选?

你可能会问:到底用 TRNG 还是 PRNG?其实这不是二选一的问题。我画了一张图,帮你理清思路:

密钥生成策略决策流程 需要生成密钥 密钥使用频率? 低频(一次性) 直接使用 TRNG 高频(多次使用) TRNG 种子 + PRNG 后处理 + 健康检测 输出安全密钥 低频场景:烧录时一次性生成主密钥 高频场景:运行时频繁生成会话密钥

简单总结一下我的经验:

  • 主密钥、根密钥:用 TRNG 直接生成,一次搞定,后面不再变
  • 会话密钥、派生密钥:用 TRNG 种子 + PRNG 生成,效率高,且安全
  • 测试密钥:可以用 PRNG 固定种子,方便复现调试

3.4 密钥派生函数(KDF)

KDF 的作用,是从一个已有的密钥材料(比如主密钥、密码、共享秘密)派生出多个子密钥。你想想看,一个设备里可能需要加密密钥、认证密钥、固件签名密钥……总不能每个都单独生成吧?KDF 就是干这个的。

我常用的 KDF 方案:

KDF 类型 输入 输出 典型标准
HKDF IKM(初始密钥材料)+ Salt + Info 任意长度派生密钥 RFC 5869
PBKDF2 密码 + Salt + 迭代次数 固定长度派生密钥 RFC 2898
SP 800-108(Counter/Feedback) K(密钥)+ Label + Context 固定长度派生密钥 NIST SP 800-108

我个人最推荐 HKDF。为什么?因为它设计简洁,安全性有严格证明,而且支持「信息标签」(Info 参数),可以区分不同用途的子密钥。比如:

// 从主密钥派生不同用途的子密钥
主密钥 = TRNG 生成(32 字节)

加密密钥 = HKDF-Expand(主密钥, info="encryption-key", length=16)
认证密钥 = HKDF-Expand(主密钥, info="auth-key", length=32)
固件密钥 = HKDF-Expand(主密钥, info="firmware-key", length=16)

实用技巧:Info 参数一定要包含上下文信息,比如设备 ID、密钥用途、版本号。这样即使两个设备的主密钥相同(理论上概率极低),派生出来的子密钥也不同。

3.5 工程实践中的避坑指南

做了这么多年嵌入式安全,我踩过的坑不少。下面这几个,你一定要注意:

坑一:TRNG 输出直接当密钥用

我曾经在一个项目中,看到同事把 TRNG 的原始输出直接写入密钥存储区。结果熵源偶尔输出全 0 或全 1 的序列,导致密钥无效。正确做法:TRNG 输出必须经过 NIST SP 800-90B 健康检测,再通过后处理(如去偏、压缩)才能使用。

坑二:PRNG 种子不够随机

有个客户用系统 tick 作为 PRNG 种子,结果攻击者通过分析设备启动时间,成功预测了所有密钥。记住:PRNG 的种子必须来自 TRNG,且种子长度至少 256 位。

坑三:KDF 的 Salt 重复使用

如果同一批设备使用相同的 Salt 派生密钥,攻击者可以通过离线攻击破解所有设备。每个设备的 Salt 必须唯一,建议用设备序列号或芯片唯一 ID 作为 Salt 的一部分。

3.6 我的推荐方案

说了这么多,直接给一个我常用的方案吧:

  1. 主密钥生成:使用芯片内置 TRNG,输出经过 NIST SP 800-90B 健康检测,再通过冯·诺依曼去偏算法处理,得到 256 位主密钥
  2. 子密钥派生:使用 HKDF(RFC 5869),以主密钥为 IKM,设备唯一 ID 为 Salt,密钥用途为 Info,派生各功能子密钥
  3. 运行时随机数:使用 CTR_DRBG(AES-256),种子由 TRNG 定期更新,用于生成会话密钥和随机数

这个方案我在多个量产项目中验证过,安全性和性能都经得起考验。你想想看,TRNG 保证了主密钥的不可预测性,HKDF 保证了子密钥的独立性和安全性,CTR_DRBG 保证了运行时的效率——三者配合,才是工程上的最优解。