4、安全启动流程设计:Bootloader的分区设计

安全启动,说白了就是让芯片只运行你授权的代码。我刚开始做电机控制时,觉得这玩意儿是给军工用的,民品没必要。直到有一次,客户现场批量报故障,一查是有人把非法固件刷进去了……嗯,从那以后,我再也不敢轻视安全启动。

这一章,我们重点聊三个东西:分区怎么划、信任根怎么建、多级架构怎么搭。

4.1 分区设计:Bootloader、Application、Backup

Flash分区是安全启动的物理基础。我个人习惯把Flash至少分成三个区:Bootloader区、Application主区、Backup备份区。为什么是三个?你想想看,如果只有Bootloader和Application,升级过程中断电了怎么办?系统就变砖了。

下面是我在STM32G4上一个典型的分区布局:

分区名称 起始地址 大小 作用
Bootloader 0x08000000 32 KB 安全启动、固件校验、升级入口
Application 0x08008000 128 KB 电机控制主程序
Backup 0x08028000 128 KB 升级备份、回滚用
Parameter 0x08048000 16 KB 校准参数、运行日志
我的经验:Bootloader区一定要放在Flash起始地址,因为MCU上电默认从0x08000000取向量表。我曾经见过有人把Bootloader放在后面,结果上电直接跑飞了。

分区设计时,有几点要注意:

  • Bootloader区要足够小——32KB通常够用,包含安全校验和升级协议就行。别往里面塞驱动库,那是Application的事。
  • Backup区必须和Application区一样大——否则升级时没法做完整备份。我见过有人把Backup只留64KB,Application却用了128KB,结果升级一半空间不够,直接翻车。
  • Parameter区单独划出来——电机控制的PID参数、编码器校准值,这些不能跟着固件一起擦除。我习惯用最后16KB存这些,每次升级前先读出来,升级完再写回去。

4.2 信任根建立:从硬件到软件

信任根(Root of Trust)是整个安全启动的基石。说白了,就是芯片上电后执行的第一段代码,它必须是绝对可信的。

信任根怎么建立?我把它分成三个层次:

  1. 硬件信任根——MCU内部ROM固件,出厂就烧好了,不可修改。比如STM32的System Memory,里面存了出厂Bootloader。这段代码是硬件保证的,没人能改。
  2. 一级信任根——我们自己写的Bootloader,放在Flash起始地址。它必须被硬件信任根校验通过后才能执行。
  3. 二级信任根——Application固件,被Bootloader校验通过后才能运行。

核心原则:每一级只信任上一级,上一级只校验下一级的签名和完整性。这就是所谓的「链式信任」。

我在项目中遇到过一个问题:某次升级后,Bootloader把自己给覆盖了,导致整个系统变砖。从那以后,我强制规定——Bootloader区在运行时是只读的,只有通过特殊指令才能擦写。这个保护机制,很多MCU都有,比如STM32的RDP(读保护)和PCROP(专有代码保护)。

避坑指南:我曾经在量产前发现,Bootloader的校验算法用了MD5,结果被客户安全审计直接打回。现在行业标准是SHA-256起步,有条件上ECC或RSA签名。别图省事用CRC,那只能检错,不能防篡改。

4.3 多级安全启动架构

多级安全启动,就是把信任链拉长,每一级都做一次校验。为什么需要多级?因为单级校验有个致命问题——如果Bootloader本身被篡改了,那它校验Application的结果就毫无意义。

下面这张图,是我常用的三级安全启动架构:

三级安全启动架构 硬件信任根 芯片ROM Bootloader 校验通过 一级Bootloader Flash 0x08000000 校验通过 Application 电机控制 校验失败 进入恢复模式 等待升级指令 Backup备份区 回滚备用 回滚 启动流程说明: 1. 上电 → 硬件信任根校验一级Bootloader签名 2. 一级Bootloader校验Application签名和完整性 3. 校验通过 → 跳转执行Application 4. 校验失败 → 从Backup区回滚,或进入恢复模式等待升级 5. 所有校验使用SHA-256 + RSA签名,密钥存储在OTP区

这个架构的核心逻辑是:每一级只信任上一级,上一级只校验下一级。硬件信任根是物理不可篡改的,所以整个信任链是安全的。

4.4 启动链的信任根建立细节

信任根建立,不只是概念,得有具体实现。我以STM32G4为例,说说实际怎么做。

第一步:硬件信任根校验

STM32G4上电后,硬件自动从0x08000000读取前4个字节(堆栈指针)和接下来的4个字节(复位向量)。如果这两个值看起来合法,就直接跳转到复位向量执行。这一步,硬件不做签名校验——它只保证代码能跑起来。

第二步:Bootloader自校验

我们自己写的Bootloader,必须在跳转到Application之前,先校验自己的完整性。怎么做?

// Bootloader自校验伪代码
void bootloader_self_check(void) {
    uint32_t hash_boot[8];  // SHA-256输出
    uint32_t hash_expected[8];
    
    // 从OTP区读取预存的Bootloader哈希值
    otp_read(OTP_BOOT_HASH, hash_expected, 32);
    
    // 计算当前Bootloader区的SHA-256
    sha256_calc(FLASH_BOOT_ADDR, FLASH_BOOT_SIZE, hash_boot);
    
    // 比较
    if (memcmp(hash_boot, hash_expected, 32) != 0) {
        // Bootloader被篡改,进入安全模式
        enter_safe_mode();
    }
}
注意:Bootloader的哈希值不能存在Flash里,否则攻击者可以同时篡改代码和哈希值。我习惯存在OTP(一次性可编程)区,出厂时烧进去,后面就改不了了。

第三步:Application校验

Bootloader校验通过后,接下来校验Application。这里我推荐用签名校验,不只是哈希。因为哈希只能防意外篡改,防不了恶意攻击。签名校验需要私钥签名、公钥验证,私钥只有开发团队有,攻击者拿不到。

// Application签名校验伪代码
bool verify_application_signature(void) {
    uint8_t signature[256];  // RSA-2048签名
    uint8_t public_key[256]; // 公钥
    uint8_t hash_app[32];    // SHA-256
    
    // 读取Application末尾的签名
    read_flash(APP_SIGNATURE_ADDR, signature, 256);
    
    // 读取公钥(从OTP区)
    otp_read(OTP_PUBLIC_KEY, public_key, 256);
    
    // 计算Application的哈希
    sha256_calc(FLASH_APP_ADDR, FLASH_APP_SIZE, hash_app);
    
    // RSA验证
    if (rsa_verify(hash_app, signature, public_key) == 0) {
        return true;  // 签名有效
    }
    return false;
}
避坑指南:我曾经在项目里把公钥直接编译进Bootloader的代码段,结果安全审计时被指出——如果攻击者能篡改Bootloader,公钥也能被改。正确的做法是:公钥存在OTP区,或者用硬件唯一ID(UID)绑定。STM32的UID是96位,出厂唯一,可以用来派生密钥。

4.5 多级安全启动的实战要点

说了这么多理论,最后总结几个实战中容易踩的坑:

  • 校验顺序不能乱——必须先校验Bootloader自己,再校验Application。我见过有人反过来,先校验Application,结果Bootloader被篡改了都不知道。
  • 密钥管理要严格——私钥不能放在代码仓库里,更不能硬编码在源码中。我习惯用HSM(硬件安全模块)存储私钥,每次签名都走HSM,密钥永远不落地。
  • 回滚机制要可靠——升级失败时,Bootloader必须能从Backup区恢复。我遇到过一种情况:Backup区里的固件也是坏的,结果回滚后还是跑不起来。所以,Backup区的固件在写入时也要做完整性校验。
  • 启动时间要控制——多级校验会增加启动时间。SHA-256计算128KB的固件,大约需要50ms。如果再加上RSA签名验证,总共可能到200ms。对于电机控制来说,这个时间通常可以接受,但如果你做的是伺服驱动器,200ms的启动延迟可能就不行了。这时候可以考虑用硬件加速器,比如STM32的CRYP外设。

我的建议:安全启动不是一蹴而就的。如果你的产品对安全要求不高,可以先做一级校验(Bootloader校验Application)。等后面产品迭代了,再逐步加上多级校验和签名验证。别一开始就想搞个大而全的方案,容易把自己绕进去。