4、安全策略:安全启动链验证、升级包完整性校验(哈希校验)、通信加密(TLS/mTLS)、防回滚机制

聊到OTA升级,很多人第一反应是「把新固件发下去,设备收到后刷进去就行了」。

嗯,如果真这么简单,那嵌入式安全工程师怕是要集体失业了。

我做过一个新能源车控的项目,当时客户说「我们只要升级功能,安全后面再补」。结果呢?测试阶段就被人用伪造的升级包把一台测试车刷成了砖。从那以后,我经手的每一个OTA项目,安全策略都是第一优先级。

今天咱们就掰开揉碎,把OTA安全的四个核心防线讲清楚。

4.1 安全启动链验证:从第一行代码开始信任

安全启动链,说白了就是「谁也别想在我启动时插队」。

设备上电后,最先执行的是固化在ROM里的BootROM代码。这段代码是硬件写死的,改不了。它要做的第一件事——验证下一级Bootloader的签名。

Bootloader验证通过后,再由它去验证OS Kernel。Kernel验证通过后,再去验证应用程序和关键服务。

这就是一条信任链:

BootROM (不可变) 
    → 验证 Bootloader (签名校验)
        → 验证 OS Kernel (签名校验)
            → 验证 系统服务/应用 (签名校验)
                → 系统正常运行

每一级只信任上一级签过名的代码。只要有一环校验失败,系统就拒绝启动。

关键点:公钥必须存储在一次性写入的OTP(One-Time Programmable)区域,或者硬件安全模块(HSM)内部。绝对不能放在Flash里,否则攻击者直接替换公钥,你的信任链就形同虚设。

我见过一个方案,把公钥放在外部SPI Flash里,还美其名曰「方便更新」。结果呢?攻击者用编程器直接读出Flash内容,替换公钥后刷入恶意固件,整条安全链瞬间崩塌。

我的习惯:公钥烧录后,我会额外做一次「读回校验」,确认写入的值和预期一致。这一步虽然简单,但能避免产线烧录时的偶发错误。

4.2 升级包完整性校验:哈希校验

升级包在传输过程中,可能被篡改,也可能因为网络问题出现数据损坏。哈希校验就是用来解决这个问题的。

具体做法:

  1. 云端对升级包计算哈希值(比如SHA-256),然后用私钥对这个哈希值签名。
  2. 设备端收到升级包后,先用公钥验证签名,得到原始的哈希值。
  3. 再对收到的升级包重新计算哈希值,对比两者是否一致。

这里有个容易踩的坑——哈希算法选择。

避坑指南:我曾经在一个项目里看到有人用MD5做升级包校验。MD5的碰撞攻击在2004年就被公开了,攻击者完全可以构造两个不同内容但MD5值相同的文件。现在最低要求是SHA-256,如果芯片算力允许,我建议直接上SHA-384或SHA-512。

另外,哈希校验的粒度也值得注意。有些方案只对整个升级包做一次哈希校验,这其实不够安全。

我建议的做法是:

  • 分块哈希:把升级包分成若干块(比如每块64KB),每块单独计算哈希值。
  • 增量校验:设备端每接收并写入一块,就立即校验该块的哈希值。一旦发现错误,马上请求重传这一块,而不是等到整个包下载完才发现问题。

你想想看,一个500MB的升级包,下载了半小时,最后校验失败要全部重来,这体验得多糟糕。

4.3 通信加密:TLS/mTLS

升级包在网络上传输,如果明文传输,等于把家门钥匙挂在门外。

TLS(传输层安全协议)是目前最成熟的选择。设备端和云端建立TLS连接后,所有数据都是加密传输的,中间人无法窃听也无法篡改。

但这里有个细节——单向TLS还是双向TLS(mTLS)?

类型 验证方式 适用场景
单向TLS 客户端验证服务器证书 设备只连接固定云端,云端不关心设备身份
双向TLS (mTLS) 客户端和服务器互相验证证书 云端需要确认设备合法身份,防止伪造设备接入

我个人强烈建议新能源架构采用mTLS。为什么?

因为新能源车联网场景下,云端不仅要给设备发升级包,还要接收设备上报的车辆状态、电池数据等敏感信息。如果只做单向TLS,攻击者可以伪造一个设备接入云端,窃取其他车辆的合法升级包,或者注入虚假数据。

实际经验:设备端的证书存储是个大问题。我见过直接把PEM格式的证书文件放在文件系统里的做法,这等于没加密。正确的做法是:

  • 使用硬件安全模块(HSM)或安全元件(SE)存储私钥
  • 如果芯片没有HSM,至少要用芯片内置的唯一ID(如UID)对私钥进行加密存储
  • 证书可以公开,但私钥必须物理隔离

4.4 防回滚机制:守住最后一道防线

防回滚,就是防止设备被刷回有漏洞的旧版本。

攻击者可能会这样做:先研究某个旧版本固件的漏洞,然后伪造一个升级包,把设备「降级」到那个有漏洞的版本,再利用漏洞获取系统控制权。

防回滚的核心思路很简单——设备端记录当前允许运行的最低版本号。

具体实现方式:

  1. 版本号比较:升级包中携带版本号,设备端比较新版本号是否大于等于当前版本号。如果小于,拒绝升级。
  2. 版本号存储位置:版本号必须存储在安全区域(如OTP或HSM的寄存器中),不能放在普通Flash里。
  3. 版本号递增规则:版本号只能单向递增,不能回退。

我曾经踩过的坑:有个项目把版本号存在外部EEPROM里,想着「反正EEPROM也不容易坏」。结果测试时发现,只要用编程器把EEPROM里的版本号改小,就能轻松降级到旧版本。后来我们改成了把版本号写入MCU内部的一次性可编程(OTP)区域,才算真正锁死。

另外,防回滚还有一个容易被忽略的点——版本号溢出

假设版本号用uint32_t表示,最大值是4294967295。如果版本号从0开始,每次升级加1,总有一天会溢出。溢出后版本号变成0,所有旧版本都能刷进去。

我的建议是:

  • 版本号不要从0开始,可以从一个较大的基数开始(比如10000)
  • 版本号递增步长可以大于1(比如每次加10),留出中间值给紧急补丁
  • 在版本号接近最大值时,通过特殊流程(如线下刷机)重置版本号计数器

4.5 四道防线的协同工作

这四道防线不是孤立的,它们需要协同工作:

  1. 通信加密(TLS/mTLS)保证升级包在传输过程中不被窃听和篡改
  2. 哈希校验保证设备收到的升级包内容完整、未被篡改
  3. 安全启动链保证升级后的固件在启动时被正确验证
  4. 防回滚保证设备不会被降级到有漏洞的旧版本

举个例子你就明白了:

假设攻击者截获了你的升级包,他无法解密(TLS保护)。就算他拿到了加密前的升级包,也无法伪造签名(哈希校验保护)。就算他绕过了签名校验,刷入了恶意固件,设备启动时也会因为签名不匹配而拒绝启动(安全启动链保护)。就算他找到了一个旧版本的漏洞,想降级回去,防回滚机制也会拦住他。

总结一句话:四道防线,缺一不可。少了一道,你的OTA升级就像没锁门的保险柜——看着安全,一推就开。

好了,安全策略这块就聊到这儿。下一章咱们讲讲升级过程中的异常处理——升级到一半断电了怎么办?网络断了怎么办?这些「万一」的情况,才是真正考验系统设计的地方。