4. 加密与签名机制:AES-128/256固件加密、ECDSA签名验证、安全启动链(Secure Boot)实现
说到固件保护,加密和签名是绕不开的两道坎。很多工程师觉得“我只要把固件藏好就行了”,但说实话,物理访问权限一旦被拿到,Flash里的二进制数据就是透明的。我见过不少产品,用逻辑分析仪直接就能把固件dump出来,连加密都没做。
这一章我们聚焦三个核心点:AES固件加密、ECDSA签名验证、以及安全启动链的完整实现。这三者组合起来,基本能挡住90%以上的固件篡改和逆向攻击。
4.1 AES-128/256 固件加密实战
AES是目前最主流的对称加密算法。128位和256位的区别,说白了就是密钥长度不同,安全强度也不同。我个人习惯:对性能敏感的MCU用AES-128,对安全等级要求高的用AES-256。
为什么?因为AES-256的密钥扩展轮数更多,加解密耗时大约是AES-128的1.4倍。在资源受限的嵌入式设备上,这个差距是能感知到的。
4.1.1 加密模式选择
AES有多种工作模式,我直接给结论:
| 模式 | 特点 | 推荐场景 |
|---|---|---|
| ECB | 相同明文块产生相同密文块,有模式泄露风险 | 不推荐,除非数据量极小且无规律 |
| CBC | 需要IV,密文块依赖前一块,安全性较好 | 固件加密常用,但注意IV不能重复 |
| CTR | 可并行加解密,支持随机访问 | 适合流式固件更新场景 |
| GCM | 同时提供加密和认证,抗篡改 | 推荐,但需要硬件加速支持 |
我在项目中遇到过一个问题:用CBC模式加密固件,但每次升级时IV都固定。结果攻击者通过对比两次升级的密文差异,直接推断出了部分明文内容。嗯,这里要注意——IV必须随机生成,且每次升级都要更换。
4.1.2 代码示例:AES-128 CBC固件加密
// 使用mbedTLS库实现AES-128 CBC加密
#include "mbedtls/aes.h"
#define AES_KEY_SIZE 16 // 128位
#define IV_SIZE 16
int encrypt_firmware(uint8_t *plaintext, size_t len,
uint8_t *key, uint8_t *iv,
uint8_t *ciphertext) {
mbedtls_aes_context aes_ctx;
int ret;
// 初始化上下文
mbedtls_aes_init(&aes_ctx);
// 设置加密密钥
ret = mbedtls_aes_setkey_enc(&aes_ctx, key, AES_KEY_SIZE * 8);
if (ret != 0) {
// 密钥设置失败,常见原因是密钥长度不对
return -1;
}
// 执行CBC加密
ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT,
len, iv, plaintext, ciphertext);
if (ret != 0) {
// 加密失败,检查输入长度是否为16字节对齐
return -2;
}
mbedtls_aes_free(&aes_ctx);
return 0;
}
4.2 ECDSA签名验证
加密只能防止固件被直接读取,但防不住攻击者篡改。这时候就需要签名了。ECDSA(椭圆曲线数字签名算法)相比RSA,密钥更短、签名更快、安全性更高。256位的ECDSA,安全强度相当于3072位的RSA。
你想想看,在MCU上做一次RSA-2048签名验证,可能要几百毫秒;而ECDSA P-256,几十毫秒就搞定了。这对启动时间敏感的设备来说,差距是巨大的。
4.2.1 签名与验证流程
- 生成密钥对:私钥保存在安全的生产环境中,公钥烧录到设备OTP或eFuse中。
- 计算固件哈希:对固件做SHA-256哈希,得到摘要。
- 签名:用私钥对摘要进行ECDSA签名,生成签名值(r, s)。
- 验证:设备端用公钥解密签名,与本地计算的哈希比对。
4.2.2 代码示例:ECDSA签名验证
// 使用mbedTLS验证ECDSA签名
#include "mbedtls/ecdsa.h"
#include "mbedtls/sha256.h"
int verify_firmware_signature(uint8_t *firmware, size_t fw_len,
uint8_t *signature, size_t sig_len,
mbedtls_ecp_keypair *pub_key) {
uint8_t hash[32];
int ret;
// 1. 计算固件SHA-256哈希
mbedtls_sha256(firmware, fw_len, hash, 0);
// 2. 验证签名
ret = mbedtls_ecdsa_read_signature(pub_key, hash, sizeof(hash),
signature, sig_len);
if (ret != 0) {
// 签名验证失败,固件可能被篡改
return -1;
}
return 0; // 验证通过
}
4.3 安全启动链(Secure Boot)实现
安全启动链,说白了就是从芯片上电到应用运行,每一步都要验证上一级的完整性和真实性。这是一个链式信任模型:
每一级在加载下一级之前,都要验证其签名。如果任何一级验证失败,系统必须进入安全状态(比如死循环或进入恢复模式)。
4.3.1 安全启动流程图
下面这张图展示了典型的安全启动链流程:
4.3.2 实现要点
- 信任根(Root of Trust):必须是硬件不可修改的,比如ROM代码或eFuse中的公钥哈希。
- 链式验证:每一级只验证下一级的签名,不跨级验证。这样可以减少ROM代码的复杂度。
- 回滚保护:固件版本号必须递增,防止攻击者降级到有漏洞的旧版本。
- 安全状态:验证失败时,不能只是“返回错误码”,必须进入不可恢复的安全状态。
4.4 综合实现:AES加密 + ECDSA签名 + 安全启动
把这三者组合起来,才是完整的固件保护方案:
- 生产阶段:用AES密钥加密固件,然后用ECDSA私钥对加密后的固件签名。
- 分发阶段:加密固件和签名一起打包,通过OTA或烧录器下发。
- 启动阶段:安全启动链先验证签名,通过后再用AES密钥解密固件,最后加载执行。