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 生成,效率高,且安全
- 测试密钥:可以用 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 我的推荐方案
说了这么多,直接给一个我常用的方案吧:
- 主密钥生成:使用芯片内置 TRNG,输出经过 NIST SP 800-90B 健康检测,再通过冯·诺依曼去偏算法处理,得到 256 位主密钥
- 子密钥派生:使用 HKDF(RFC 5869),以主密钥为 IKM,设备唯一 ID 为 Salt,密钥用途为 Info,派生各功能子密钥
- 运行时随机数:使用 CTR_DRBG(AES-256),种子由 TRNG 定期更新,用于生成会话密钥和随机数
这个方案我在多个量产项目中验证过,安全性和性能都经得起考验。你想想看,TRNG 保证了主密钥的不可预测性,HKDF 保证了子密钥的独立性和安全性,CTR_DRBG 保证了运行时的效率——三者配合,才是工程上的最优解。