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 填充字节,加密后一眼就能看出规律。

警告: 绝对不要在固件加密中使用 ECB 模式!我曾经接手过一个客户的项目,他们用 ECB 加密了 Bootloader,结果攻击者通过对比不同地址的密文块,直接还原出了代码结构。

我推荐使用 AES-CBCAES-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 里
  • 密钥轮换:定期或按条件更换密钥

具体怎么做?我推荐一种实用的三层密钥体系:

  1. 根密钥(Root Key):烧录在芯片的 OTP(一次性可编程)区域或 eFuse 中,出厂后不可读取。这是整个信任链的锚点。
  2. 设备密钥(Device Key):由根密钥派生,每个设备不同。用于加密本地存储的固件和配置数据。
  3. 会话密钥(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),到时候你会看到,今天讲的这些加密技术是怎么串联起来,形成一条完整的信任链的。

记住一句话:加密算法是骨架,密钥管理是灵魂。骨架可以买现成的,灵魂得自己用心打造。