4. 密码学基础(下):数字签名(ECDSA)、消息认证码(HMAC)、密钥派生函数(KDF)在MCU中的应用
好,咱们接着聊。上一节我们把哈希和对称加密讲透了,这一节要啃的硬骨头是数字签名、消息认证码和密钥派生。说实话,这三样东西在MCU开发里,才是真正决定你产品“安不安全”的关键。
很多工程师觉得,我用了AES加密,数据就安全了。其实不然。你想想看,如果攻击者篡改了你的固件包,你拿什么去验证?如果通信双方密钥泄露了,你拿什么去派生新密钥?嗯,这就是我们今天要解决的问题。
4.1 数字签名(ECDSA)—— 你是谁?你说了算不算?
数字签名,说白了就是“电子印章”。在MCU世界里,最常见的场景就是固件升级验证。我记得刚入行那会儿,有个产品因为没做签名校验,被黑客注入了恶意固件,整批设备都成了肉鸡。从那以后,我对签名这件事就特别敏感。
ECDSA(椭圆曲线数字签名算法)是目前MCU上最主流的签名方案。为什么选它?因为同样的安全强度下,它的密钥更短,计算更快。比如256位的ECDSA,安全性和3072位的RSA差不多,但签名体积小得多——这对Flash寸土寸金的MCU来说,太重要了。
核心流程:
- 签名(固件发布方): 用私钥对固件哈希值进行签名,生成签名文件(r, s)。
- 验签(MCU设备端): 用预置的公钥,对收到的固件+签名进行验证。通过则执行,不通过则拒绝。
这里有个坑,我必须要说。ECDSA的随机数生成器如果不够随机,私钥就可能被推导出来。我曾经在项目里遇到过,某款MCU的硬件随机数发生器在刚上电时输出质量很差,导致签名时用了弱随机数。结果呢?攻击者通过两次签名就反推出了私钥。所以,签名时一定要用高质量的TRNG(真随机数发生器),或者至少用NIST SP 800-90A认证的DRBG。
我的个人习惯: 在MCU上做ECDSA验签时,我会把公钥存储在OTP(一次性可编程)区域,或者用芯片唯一ID绑定。这样即使攻击者读出了Flash,也无法替换公钥。
4.2 消息认证码(HMAC)—— 消息没被改过,而且是你发的
数字签名能解决“谁发的”问题,但开销比较大。如果你只是需要确认“消息没被篡改,且来自持有密钥的对方”,那HMAC(基于哈希的消息认证码)是更轻量的选择。
HMAC的原理很简单:把密钥和消息混在一起做哈希。但怎么混,是有讲究的。标准做法是:
HMAC(K, m) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || m ) )
其中K'是经过填充的密钥,ipad和opad是固定的填充字节。为什么要搞这么复杂?直接H(K || m)不行吗?
不行。我踩过这个坑。早期有个项目,我图省事用了H(K || m)做消息认证。结果发现,如果攻击者知道H(K || m)的值,他可以在不知道K的情况下,构造出H(K || m || padding || extra) —— 这就是经典的“长度扩展攻击”。HMAC通过两次哈希和异或操作,彻底杜绝了这个问题。
避坑指南: 我曾经在STM32上实现HMAC-SHA256时,直接调用了mbedTLS的API。但要注意,有些低端MCU的硬件哈希加速器不支持HMAC模式,只能用软件实现。这时候要评估一下性能——比如在48MHz的Cortex-M0上,做一次HMAC-SHA256大概要花几毫秒,如果通信频率很高,可能会成为瓶颈。
HMAC在MCU上的典型应用场景:
- 传感器数据完整性校验: 每个数据包附带一个HMAC标签,接收方验证。
- 安全通道的会话认证: 比如TLS握手过程中的Finished消息。
- 固件分片校验: 大固件拆成小块传输,每块带HMAC,防止中间人篡改。
4.3 密钥派生函数(KDF)—— 一把钥匙开多把锁
你有没有想过一个问题:如果通信双方只用同一个密钥做加密和认证,一旦这个密钥泄露,所有安全保护都失效了。正确的做法是:用KDF从一个主密钥派生出多个子密钥,分别用于加密、认证、初始化向量等。
KDF的核心思想是:输入一个“密钥材料”(比如DH协商出的共享密钥),加上一些“上下文信息”(比如双方ID、会话编号),输出一个或多个固定长度的密钥。
在MCU上,我最常用的KDF是HKDF(基于HMAC的密钥派生函数)。它分两步:
- 提取(Extract): 用HMAC将输入的密钥材料“浓缩”成一个固定长度的伪随机密钥。
- 扩展(Expand): 用HMAC将这个伪随机密钥扩展出任意长度的子密钥。
实际项目中的用法:
// 假设我们通过ECDH协商出了一个32字节的共享密钥 shared_secret
// 现在要派生出:AES-128加密密钥(16字节) + HMAC-SHA256密钥(32字节)
// 第一步:提取
uint8_t prk[32];
hkdf_extract(prk, shared_secret, 32, salt, 16);
// 第二步:扩展
uint8_t aes_key[16];
uint8_t hmac_key[32];
hkdf_expand(aes_key, 16, prk, 32, "enc-key", 7);
hkdf_expand(hmac_key, 32, prk, 32, "mac-key", 7);
这里有个细节要注意:salt(盐值)不能是固定的。我见过有人把salt写死在代码里,那跟没用salt有什么区别?正确的做法是:每次会话生成一个随机salt,或者用双方交换的随机数作为salt。这样即使主密钥相同,派生出的子密钥也不同——这叫“前向安全性”。
我的建议: 在MCU上实现KDF时,尽量复用已有的HMAC硬件加速器。比如NXP的LPC55系列,它的CASPER引擎可以加速HMAC计算,用HKDF时性能提升很明显。如果MCU没有硬件加速,那就用mbedTLS或WolfSSL的软件实现,它们对Flash和RAM的占用都做过优化。
4.4 三者在MCU中的协同工作
讲完了各自的理论,咱们看看它们在实际项目中是怎么配合的。我拿一个典型的“安全固件升级”流程举例:
| 步骤 | 使用技术 | 说明 |
|---|---|---|
| 1. 固件发布方签名 | ECDSA | 用私钥对固件哈希签名,生成签名文件 |
| 2. 设备端验签 | ECDSA | 用预置公钥验证签名,确认固件来源可信 |
| 3. 固件传输加密 | AES + HMAC | 用会话密钥加密固件,每包附加HMAC保证完整性 |
| 4. 会话密钥派生 | HKDF | 从预共享密钥或DH协商结果派生出加密密钥和HMAC密钥 |
| 5. 解密并写入Flash | AES | 解密后写入,写入前再次校验整体哈希 |
你看,ECDSA、HMAC、KDF各司其职,缺一不可。ECDSA解决“谁发的”,HMAC解决“被改没”,KDF解决“密钥怎么用”。
最后说一句:密码学算法本身是安全的,但实现和集成才是漏洞的温床。我见过太多“算法选对了,但用错了”的案例。比如ECDSA的随机数重用、HMAC的密钥硬编码、KDF的salt固定——这些坑,我几乎都踩过。希望今天的分享,能帮你少走一些弯路。
下一节,我们会把这些技术真正落地到MCU代码里,手把手教你写一个安全启动的Bootloader。到时候见。