4、密码学基础(下):数字签名、消息认证码(HMAC)、密钥派生函数(KDF)、随机数生成器
好,咱们接着聊密码学。上一节我们把对称加密、非对称加密和哈希函数过了一遍。这一节要讲的四个东西——数字签名、HMAC、KDF 和随机数生成器——在实际的固件安全方案里,几乎天天都要打交道。
我个人习惯把这一节叫做「实战四件套」。为什么?因为你在做安全启动、固件加密、密钥管理的时候,绕不开它们。你想想看,没有数字签名,你怎么验证固件是谁发的?没有 HMAC,你怎么保证固件没被篡改?没有 KDF,你怎么从一串密码派生出安全的密钥?没有好的随机数,你的密钥跟写在纸上有什么区别?
好,一个一个来。
4.1 数字签名:证明「你是谁」和「你没改过」
数字签名解决的是两个问题:身份认证和数据完整性。说白了,就是让接收方能确认「这东西确实是张三发的」并且「发出来之后没人动过」。
它的原理其实不复杂。用私钥签名,用公钥验签。我在项目中遇到过好几次,有人把签名和加密搞混了。加密是为了保密,签名是为了证明身份。两码事。
数字签名流程(以 ECDSA 为例):
- 发送方:对固件哈希值用私钥做签名,生成签名值
- 接收方:用公钥对签名值做验签,比对哈希值
嗯,这里要注意:签名不是对整个固件做的,是对固件的哈希值做的。为什么?因为非对称加密慢啊,一个固件几十兆,你直接签名得等到天荒地老。先算哈希,再签哈希,这是行业标准做法。
我曾经在一个 IoT 项目里看到有人直接对固件做 RSA 签名,结果每次 OTA 升级要等两分钟。后来改成先 SHA256 再签名,时间降到了 0.3 秒。你看,细节决定体验。
实战建议:在嵌入式设备上,优先使用 ECDSA 而不是 RSA。同样的安全强度下,ECDSA 的密钥更短,签名速度更快。我一般用 NIST P-256 曲线,安全性和性能比较均衡。
4.2 消息认证码(HMAC):共享秘密下的完整性校验
数字签名需要公钥私钥对,成本高。如果你和对方已经共享了一个密钥,那用 HMAC 更轻量。
HMAC 的全称是 Hash-based Message Authentication Code。它本质上就是「带密钥的哈希」。普通的哈希函数(比如 SHA256)没有密钥,谁都能算。但 HMAC 加了一把钥匙,只有知道密钥的人才能生成和验证。
我举个例子。你在智能音箱和云端之间通信,每次发指令的时候,附带上一个 HMAC 值。云端用同样的密钥重新计算一遍,如果匹配,说明这条指令确实是音箱发的,而且中途没人篡改过。
避坑指南:我曾经见过有人直接用「密钥 + 消息」拼接后做哈希,以为这就是 HMAC。这是不对的。HMAC 有标准的构造方式(两次哈希 + 异或填充),直接用拼接方式容易受到长度扩展攻击。请使用标准库,不要自己造轮子。
HMAC 的典型用法:
// 伪代码示例
key = "shared_secret_32bytes"
message = "firmware_version_1.2.3"
hmac_result = HMAC_SHA256(key, message)
在固件安全里,HMAC 常用于:
- 固件包的完整性校验(配合对称加密)
- 设备与服务器之间的通信认证
- 安全存储时的数据完整性保护
4.3 密钥派生函数(KDF):从「弱密码」到「强密钥」
KDF 这个知识点,说实话,很多人容易忽略。但它在实际项目里特别重要。
你想想看,用户输入的密码通常只有 6-8 位,熵值很低。直接拿这个当 AES 密钥?太危险了。KDF 的作用就是:把一个低熵的输入,扩展成一个高熵的、固定长度的密钥。
常见的 KDF 有:
| KDF 类型 | 特点 | 适用场景 |
|---|---|---|
| PBKDF2 | 可配置迭代次数,抗暴力破解 | 用户密码派生密钥 |
| HKDF | 基于 HMAC,可扩展输出长度 | 密钥协商后的派生 |
| Argon2 | 抗 GPU/ASIC 攻击,内存硬 | 高安全场景 |
我个人习惯在 IoT 设备上用 HKDF。为什么?因为它轻量,不需要像 PBKDF2 那样做大量迭代计算,适合资源受限的 MCU。而且 HKDF 有两个阶段——提取和扩展——非常灵活。
举个例子。你在做安全启动时,从设备唯一密钥(比如 eFuse 里烧录的根密钥)派生出多个子密钥:一个用于固件加密,一个用于 HMAC,一个用于通信。这时候 HKDF 就派上用场了。
HKDF 使用示例:
输入:IKM(初始密钥材料,比如 eFuse 中的 128 位随机数)
盐值:随机数(每次启动不同)
信息:上下文字符串,如 "firmware_enc_key"
输出:派生出的 AES-256 密钥
嗯,这里有个坑。很多人用 KDF 的时候不提供盐值(salt)。盐值的作用是防止同样的输入产生同样的输出。如果你不加固盐,两个相同型号的设备会派生出相同的密钥——这等于把整个产品线的安全都押在一个秘密上。我曾经在审计一个项目时发现这个问题,后来建议他们每次烧录时写入不同的盐值到 OTP 区域。
4.4 随机数生成器:安全的基石
最后这个,可能是最容易被低估的。随机数生成器(RNG),听起来简单,但做不好,整个密码体系就垮了。
为什么?你想想看,密钥生成需要随机数,签名时 nonce 需要随机数,KDF 的盐值需要随机数。如果随机数可预测,那攻击者就能复现你的密钥。
嵌入式设备上的随机数生成器通常分两种:
- TRNG(真随机数生成器):基于物理噪声,比如芯片内部的热噪声、振荡器抖动。硬件支持,质量高。
- PRNG(伪随机数生成器):基于算法,比如 CTR_DRBG、Hash_DRBG。需要种子,种子来自 TRNG。
我在项目中遇到过最典型的问题:有人直接用 C 语言的 rand() 函数来生成密钥。结果呢?每次上电后的密钥序列都一样。因为 rand() 的种子没变。这相当于你家门锁的密码每天都是「1234」。
警告:永远不要用 rand()、random() 或任何非密码学安全的伪随机数生成器来生成密钥、nonce 或盐值。在嵌入式平台上,优先使用硬件 TRNG,如果没有,使用芯片厂商提供的安全随机数库(比如 STM32 的 RNG 外设)。
好的随机数生成器应该满足:
- 不可预测性:即使知道之前的输出,也无法推断后续输出
- 均匀分布:0 和 1 出现的概率各 50%
- 无周期性:不会出现重复序列
我记得有一次帮客户做安全评估,他们的智能门锁用 MCU 内置的 RC 振荡器作为随机源,但没做后处理。结果发现输出的随机数有明显的偏置——0 出现的概率是 65%。这样的随机数生成的密钥,有效强度直接打了七折。
实战建议:如果硬件 TRNG 不可用,可以用多个独立噪声源混合(比如 ADC 悬空引脚的噪声、WiFi 信号强度抖动、传感器噪声),然后通过哈希函数做后处理。但说实话,这只能作为备选方案。有条件还是上硬件 TRNG。
小结
好,这一节的内容就这些。数字签名、HMAC、KDF、随机数生成器,这四个东西在智能音箱的安全启动方案里都会用到。下一节我们开始讲具体的固件加密方案,到时候你会看到这些密码学原语是怎么组合起来干活的。
记住一句话:密码学不是搭积木,搭错了会塌。每个原语都有它的正确用法和常见误区。多在实践中积累经验,慢慢就有感觉了。
公众号:蓝海资料掘金营,微信 deep3321