3、加密基础回顾:对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA-256)在固件中的应用
好,咱们进入正题。做 Bootloader 加密,说白了就是跟这三样东西打交道:对称加密、非对称加密、哈希函数。很多新手一上来就搞混,觉得都是加密嘛,有啥区别?我刚开始也这么想,直到在项目里吃了亏才明白——选错加密方式,轻则性能崩盘,重则固件被扒光。
今天我就把这三样东西在固件里的实际用法,掰开了讲清楚。你想想看,Bootloader 要干的事无非就是:验证固件是不是官方的、防止别人篡改、防止别人直接读 Flash 把代码抄走。这三件事,正好对应三种不同的密码学工具。
3.1 对称加密:AES 在固件加密中的角色
对称加密,说白了就是加密和解密用同一个密钥。就像你家里的大门,一把钥匙既能锁门也能开门。在固件领域,最常用的就是 AES(Advanced Encryption Standard)。
AES 的特点:速度快、密钥短(128/192/256 位)、适合大批量数据加密。我做过一个项目,固件大小 2MB,用 AES-128-CBC 模式加密,在 STM32F4 上跑,解密时间不到 200 毫秒。这个速度,用户基本无感。
但有个坑——密钥怎么存? 你想想看,Bootloader 里得存着这个密钥才能解密固件吧?那攻击者只要把 Flash 读出来,密钥不就暴露了?
在 Bootloader 中的典型用法:
- 固件分发时,先用 AES 加密整个固件镜像
- Bootloader 启动时,读取加密固件,用预置密钥解密
- 解密后的固件写入运行区,或者直接 XIP(原地执行)
代码示例(伪代码,实际项目中我会用硬件 AES 加速器):
// 假设密钥已经安全存储在 OTP 区域
uint8_t aes_key[16] = {0x2b, 0x7e, 0x15, 0x16, ...};
// 解密固件块
void decrypt_firmware_block(uint8_t *encrypted_data, uint32_t len) {
AES_ECB_decrypt(encrypted_data, len, aes_key);
// 注意:实际项目中建议用 CBC 或 GCM 模式,防止重放攻击
}
我个人习惯用 AES-128-GCM 模式。为什么?因为它自带认证标签,能检测数据是否被篡改。CBC 模式只加密不认证,攻击者可以翻转密文比特来搞破坏。嗯,这里要注意,GCM 模式需要额外的 IV(初始化向量),IV 可以明文存储,但每次更新固件必须换新的。
3.2 非对称加密:RSA 与 ECC 的固件签名验证
非对称加密就完全不一样了——加密和解密用不同的密钥。公钥公开,私钥保密。在固件领域,我们通常用它来做数字签名验证,而不是加密整个固件。
为什么?因为非对称加密太慢了。RSA-2048 签名验证一次,在 Cortex-M4 上可能要几百毫秒甚至几秒。你想想看,要是用 RSA 解密整个 2MB 固件,用户得等到天荒地老。
实际做法是这样的:
- 服务器用私钥对固件的哈希值签名(生成签名数据)
- 固件包 = 加密的固件数据 + 签名数据
- Bootloader 用公钥验证签名,确认固件来源可信
- 验证通过后,再用 AES 密钥解密固件数据
这就是典型的混合加密方案:非对称加密管认证,对称加密管保密。
RSA vs ECC 怎么选? 我个人的经验是:
| 特性 | RSA-2048 | ECC(secp256r1) |
|---|---|---|
| 密钥长度 | 2048 位 | 256 位 |
| 签名验证速度 | 较慢(约 300ms @ 200MHz) | 较快(约 50ms @ 200MHz) |
| 公钥存储空间 | 256 字节 | 32 字节(压缩格式) |
| 安全性 | 高 | 高(同等安全强度下密钥更短) |
我建议在资源受限的 MCU 上优先选 ECC。之前有个项目,Flash 只剩 4KB 空间给 Bootloader,RSA 的公钥就占了 256 字节,再加上签名验证代码,根本塞不下。换成 ECC 后,公钥只占 32 字节,整个签名验证模块压缩到了 2KB 以内。
3.3 哈希函数:SHA-256 的固件完整性校验
哈希函数,说白了就是数据指纹。不管你的固件是 1KB 还是 1GB,SHA-256 都会给你一个固定 32 字节的摘要。而且这个摘要是单向的——从摘要反推原始数据,理论上不可能。
在 Bootloader 里,SHA-256 主要干两件事:
- 完整性校验:计算固件的哈希值,跟预置的哈希值对比,看固件有没有被损坏
- 签名验证的基础:先对固件算哈希,再对哈希值做签名验证(而不是对整个固件做签名)
我遇到过最典型的场景是这样的:OTA 升级时,网络传输可能丢包或者数据错乱。固件下载到一半,突然断电重启。下次启动时,Bootloader 先算一遍 SHA-256,发现跟固件头里记录的哈希值对不上——好,说明固件坏了,直接回滚到旧版本。
// 计算固件区域的 SHA-256 哈希
uint8_t calculated_hash[32];
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, firmware_addr, firmware_size);
sha256_final(&ctx, calculated_hash);
// 跟固件头里存储的哈希对比
if (memcmp(calculated_hash, firmware_header->expected_hash, 32) != 0) {
// 哈希不匹配,固件损坏
rollback_to_previous_version();
}
这里有个细节要注意:哈希值本身也需要保护。如果攻击者能同时修改固件和哈希值,那完整性校验就形同虚设。所以哈希值要么存储在安全区域(比如 Bootloader 自己的只读区),要么用非对称签名来保护。
• AES:负责固件的机密性(防止别人读你的代码)
• RSA/ECC:负责固件的认证性(确保固件来自官方)
• SHA-256:负责固件的完整性(确保固件没有被篡改)
三者缺一不可,少一个就是安全漏洞。
最后说一句,别想着只用一种加密搞定所有事。我见过有人只用 AES 加密固件,觉得这样就安全了——结果攻击者直接替换了加密后的固件,设备照样运行,因为 Bootloader 根本没验证固件来源。嗯,这就是典型的只加密不认证的坑。
下一章我会讲怎么把这些工具组合起来,搭建一个完整的 Bootloader 安全启动链。到时候你会看到,这些基础算法是怎么串起来干活的。