4、固件加密技术:对称加密(AES)、非对称加密(RSA/ECC)、密钥管理策略
各位好,咱们接着聊固件安全。前面几章我们把硬件层面的攻击手段和防护措施捋了一遍,从今天开始,要进入真正的「加密对抗」环节了。
固件加密,说白了就是给你的代码穿上防弹衣。我见过太多产品,硬件设计得固若金汤,结果固件直接明文存在Flash里,用个逻辑分析仪就能把二进制拖出来反汇编。嗯,这种坑我年轻时也踩过。
4.1 对称加密:AES 的实战选择
对称加密,加密和解密用同一把钥匙。速度快,适合嵌入式这种资源受限的环境。目前业界公认的黄金标准就是 AES(Advanced Encryption Standard)。
我个人习惯,在嵌入式项目里首选 AES-128 或 AES-256。为什么不用 AES-192?说实话,128 位已经够用,256 位更安心,192 位夹在中间,性能和安全都讨不到好。
AES 的工作模式,这里要重点讲。很多新手直接拿 AES-ECB 模式去加密固件,这是大忌。ECB 模式下,相同的明文块会生成相同的密文块,你想想看,固件里大量重复的 0x00 填充字节,加密后一眼就能看出规律。
我推荐使用 AES-CBC 或 AES-CTR 模式。CBC 模式需要初始化向量(IV),每个块加密都依赖前一个块,打乱了数据规律。CTR 模式则把 AES 变成一个流密码,性能更好,还能支持随机访问——这对固件升级场景特别有用。
来看一段典型的 AES-CBC 加密代码(基于 mbedTLS 库):
#include <mbedtls/aes.h>
#include <mbedtls/gcm.h>
// 加密固件块
int encrypt_firmware_block(uint8_t *plaintext, uint32_t len,
uint8_t *key, uint8_t *iv,
uint8_t *ciphertext) {
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, key, 128);
// CBC模式加密
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT,
len, iv, plaintext, ciphertext);
mbedtls_aes_free(&aes);
return 0;
}
注意看,这里的 IV 每次加密都要随机生成。我见过有人把 IV 写死在代码里,那跟没用 IV 有什么区别?
4.2 非对称加密:RSA 与 ECC 的取舍
非对称加密,公钥加密、私钥解密。速度慢,但解决了密钥分发的问题。在固件安全里,它主要用来做两件事:数字签名和密钥交换。
RSA 是老牌选手,成熟、稳定。但它的密钥长度太长了——要保证安全,至少 2048 位。在 Cortex-M4 这种 MCU 上,一次 RSA-2048 签名可能要几百毫秒,用户等得心焦。
ECC(椭圆曲线加密) 是后起之秀。256 位的 ECC 密钥,安全强度相当于 3072 位的 RSA。而且 ECC 的签名和验签速度快得多,占用 Flash 和 RAM 也更少。
我个人现在做新产品,基本都转向 ECC 了。特别是 Ed25519 曲线,签名速度快,实现简单,还天然抗侧信道攻击。我在一个智能门锁项目里用过 Ed25519,Bootloader 验签一次只需要 15 毫秒,用户体验好很多。
| 算法 | 密钥长度 | 安全强度 | 签名速度(Cortex-M4) | 推荐场景 |
|---|---|---|---|---|
| RSA-2048 | 2048 bit | 112 bit | ~300 ms | 兼容旧系统 |
| RSA-4096 | 4096 bit | 128 bit | ~1.5 s | 高安全要求 |
| ECC-256 (P-256) | 256 bit | 128 bit | ~30 ms | 通用嵌入式 |
| Ed25519 | 256 bit | 128 bit | ~15 ms | 高性能需求 |
为什么会这样?因为 ECC 的数学基础是椭圆曲线上的离散对数问题,同样的安全强度下,密钥可以短很多。短密钥意味着更少的计算量、更少的存储空间。
4.3 密钥管理策略:最容易被忽视的环节
加密算法选得再好,密钥管理一塌糊涂,等于白干。我见过最离谱的项目,把 AES 密钥直接硬编码在固件里,然后用同一个密钥加密所有设备。攻击者只要破解一台,整个产品线全部沦陷。
密钥管理的核心原则:
- 一机一密:每台设备使用独立的密钥
- 密钥分级:设备密钥、固件密钥、根密钥,分层管理
- 安全存储:密钥不能明文存在 Flash 里
- 密钥轮换:定期或按条件更换密钥
具体怎么做?我推荐一种实用的三层密钥体系:
- 根密钥(Root Key):烧录在芯片的 OTP(一次性可编程)区域或 eFuse 中,出厂后不可读取。这是整个信任链的锚点。
- 设备密钥(Device Key):由根密钥派生,每个设备不同。用于加密本地存储的固件和配置数据。
- 会话密钥(Session Key):每次固件升级时临时生成,用设备公钥加密传输。用完即弃。
关键实践: 根密钥最好在芯片制造阶段就写入,而不是在 SMT 贴片后。我曾经遇到一个客户,他们在产线上用 JTAG 写入根密钥,结果被操作员偷偷读出来卖给了竞争对手。从那以后,我坚持让芯片原厂预置根密钥。
密钥派生也很讲究。不要直接用根密钥去加密数据,而是用 KDF(密钥派生函数)生成子密钥。比如:
// 使用 HMAC-SHA256 派生设备密钥
uint8_t root_key[32]; // 从 OTP 读取
uint8_t device_id[16]; // 芯片唯一 ID
uint8_t device_key[32];
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
root_key, 32,
device_id, 16,
device_key);
这样即使某个设备的密钥泄露,也影响不到其他设备。而且攻击者就算拿到了 device_key,也无法反推出 root_key——HMAC 是单向函数。
4.4 避坑指南:我踩过的那些雷
做固件加密这么多年,有些坑我是一路踩过来的。分享几个典型的:
技巧: 密钥存储位置要选对。别放在 Flash 主存储区,那里太容易被读出来。放在芯片的 MPU(内存保护单元) 保护的 SRAM 区域,或者专门的密钥存储寄存器里。有些 MCU 有 TrustZone 或 安全区域,那是更好的选择。
我曾经犯过的错:
- 用随机数生成器(RNG)直接产生密钥,结果发现芯片的 RNG 在刚上电时输出不够随机。后来我改用硬件 TRNG + 软件熵池混合的方式。
- 在固件升级时,用同一个 IV 加密所有固件包。攻击者通过对比不同版本的密文,能推断出代码改动的位置。现在每次升级我都重新生成 IV。
- 把公钥放在 Bootloader 里,以为公钥公开没关系。但攻击者可以替换公钥,用自己的私钥签名恶意固件。正确的做法是把公钥的哈希值烧录在 OTP 里,Bootloader 验签时先校验公钥完整性。
嗯,说到公钥保护,这里再强调一下。公钥虽然可以公开,但它的完整性必须保证。攻击者如果篡改了 Bootloader 里的公钥,你的整个安全体系就崩塌了。所以公钥要么放在只读区域,要么用硬件机制锁定。
4.5 小结
固件加密不是简单地把算法堆上去就行。AES 选对模式,RSA 和 ECC 按场景取舍,密钥管理要分层分级。这三者缺一不可。
下一章我们会讲安全启动(Secure Boot),到时候你会看到,今天讲的这些加密技术是怎么串联起来,形成一条完整的信任链的。
记住一句话:加密算法是骨架,密钥管理是灵魂。骨架可以买现成的,灵魂得自己用心打造。