4. 固件加密与解密:对称加密算法在固件中的应用、密钥派生与存储、OTA升级中的加密传输
说到固件安全,加密这块儿是绕不过去的坎儿。我见过不少团队,功能跑得飞起,结果一抓包,固件明文传输,密钥硬编码在代码里。嗯,这种项目基本就是给攻击者送人头。今天咱们就聊聊对称加密在固件里的那些事儿,包括怎么用、怎么存密钥、OTA升级怎么保证不被篡改。
4.1 对称加密算法在固件中的应用
对称加密,说白了就是加密和解密用同一把钥匙。在嵌入式环境里,资源受限,我们常用的就那么几种:AES、ChaCha20、SM4(国标)。我个人习惯首选AES-128-CCM或GCM模式,因为自带认证,能同时保证机密性和完整性。
核心原则:固件加密不是为了隐藏算法,而是为了隐藏密钥。算法公开没关系,密钥泄露才是灾难。
我在项目中遇到过一件事:某款智能锁固件用AES-ECB模式加密通信数据。结果呢?同样的明文块产生同样的密文块,攻击者通过重放攻击直接开了锁。所以模式选择很重要,千万别用ECB。
| 算法 | 模式 | 适用场景 | 我的建议 |
|---|---|---|---|
| AES-128 | CCM / GCM | 固件加密、OTA传输 | 首选,硬件加速普遍 |
| ChaCha20-Poly1305 | AEAD | 低端MCU、无AES硬件 | 软件实现快,抗侧信道 |
| SM4 | GCM / CBC | 国密合规场景 | 国内项目必备 |
你想想看,Matter设备里很多都是电池供电的,算力有限。AES-GCM模式虽然好,但如果MCU没有硬件加密引擎,纯软件跑起来功耗感人。这时候ChaCha20就香了,纯软件实现比AES快好几倍。
4.2 密钥派生与存储
密钥怎么来的?怎么存?这是个大问题。很多开发者图省事,直接把密钥写死在代码里。我曾经逆向过一个智能灯泡,密钥就在字符串表里,连偏移量都没变。这种操作,说实话,跟没加密一样。
正确的做法是用密钥派生函数(KDF)。比如从设备唯一ID、出厂种子、随机数一起算出一个会话密钥。Matter协议里用的是HKDF-SHA256,我个人觉得这个选择很合理。
// 密钥派生示例:基于设备序列号和随机数
uint8_t seed[16] = {0x...}; // 出厂预置种子
uint8_t serial[8]; // 设备唯一序列号
uint8_t nonce[12]; // 随机数
uint8_t derived_key[16]; // 派生出的会话密钥
hkdf_sha256(seed, sizeof(seed),
serial, sizeof(serial),
nonce, sizeof(nonce),
derived_key, sizeof(derived_key));
小技巧:派生密钥时一定要加盐(salt)。不加盐的话,同样的种子和序列号会算出同样的密钥,攻击者可以预计算彩虹表。
密钥存储就更讲究了。别放Flash明文区,那是给调试器看的。我建议用这几招:
- 硬件安全单元(HSM/Secure Element):密钥根本不出芯片,只往里送数据,拿结果。这是最安全的。
- MCU内置OTP(一次性可编程)区域:写进去就改不了,读也受限。适合存根密钥。
- Flash加密存储:如果没硬件,至少用设备唯一密钥加密后再存。但要注意,这个唯一密钥本身不能存Flash里。
避坑指南:我曾经见过一个项目,把密钥用XOR混淆后存Flash。攻击者只要读两次不同状态的Flash,一对比就能还原出原始密钥。这种“伪加密”比不加密更危险,因为它给你虚假的安全感。
4.3 OTA升级中的加密传输
OTA升级是攻击者的重点目标。你想啊,固件在空中飞,要是被中间人换了,设备就成别人的肉鸡了。Matter的OTA设计得还算严谨,但落地实现时坑不少。
一个完整的OTA加密传输流程应该是这样的:
- 设备发起请求,附带自己的证书和随机数。
- 服务器验证设备身份,生成会话密钥。
- 用会话密钥加密固件包,同时计算MAC(消息认证码)。
- 设备收到后,先验MAC,再解密,最后校验固件签名。
这里有个关键点:先验证完整性,再解密。为什么?因为如果先解密再验签,攻击者可以构造恶意密文触发解密时的缓冲区溢出。我在一次安全审计中就遇到过这种实现,差点出事。
// OTA固件包结构(简化版)
typedef struct {
uint32_t magic; // 魔数 0xOTA1
uint32_t version; // 固件版本
uint8_t iv[12]; // 初始向量
uint8_t tag[16]; // GCM认证标签
uint8_t ciphertext[]; // 加密后的固件数据
} ota_packet_t;
// 解密流程
int ota_decrypt(ota_packet_t *pkt, uint8_t *key, uint8_t *plaintext) {
// 1. 先验证认证标签
if (!aes_gcm_decrypt(pkt->ciphertext, len,
pkt->iv, key,
pkt->tag, plaintext)) {
return -1; // 完整性校验失败,丢弃
}
// 2. 再校验固件数字签名
if (!verify_signature(plaintext, len, device_pubkey)) {
return -2; // 签名无效,拒绝升级
}
return 0;
}
关键点:OTA加密传输的“加密”只是第一道防线。真正的安全基石是固件签名。加密防窃听,签名防篡改。两者缺一不可。
我建议在OTA实现中注意这几个细节:
- 每次OTA使用不同的随机IV,防止重放攻击。同一个密钥下IV重复,GCM就废了。
- 固件版本号要防回滚。攻击者可能拿旧版本固件(有漏洞的)来降级攻击。Matter里用版本号单调递增来防这个。
- 解密失败要记录日志,但不能泄露原因。比如“认证失败”和“解密失败”返回同样的错误码,防止攻击者通过错误信息猜密钥。
最后说一句,加密这事儿,别自己发明轮子。用现成的库,比如mbedTLS、OpenSSL(如果空间够)、或者芯片厂商提供的加密库。我见过有人自己写AES实现,结果S盒查表有侧信道泄漏,功耗分析直接就把密钥扒出来了。嗯,专业的事交给专业的库,咱们把精力花在架构设计和密钥管理上,这才是正道。