第1篇:什么是安全启动和安全固件更新 ------ SBSFU 概念入门
系列定位:从概念到实战,深入理解 STM32 安全启动与安全固件更新
目录
- [什么是 IAP/OTA?](#什么是 IAP/OTA?)
- 为什么需要"安全"启动?
- 为什么需要"安全"固件更新?
- [SBSFU 是什么?](#SBSFU 是什么?)
- 核心概念速览
- [单镜像 vs 双镜像](#单镜像 vs 双镜像)
- 本系列文章学习路线图
1. 什么是 IAP/OTA?
1.1 传统固件升级方式
想象一下,你买了一台智能家电(比如智能门锁或智能灯泡),半年后发现了一个bug需要修复。如果是传统的嵌入式设备,固件升级方式是这样的:
[电脑/笔记本]
|
| USB线 / J-Link / ST-LINK 调试器
|
[调试接口 SWD/JTAG]
|
|
[单片机 Flash]
你需要:
- 把设备拆开,找到电路板上的调试接口
- 用物理线缆(ST-LINK、J-Link、USB转串口)连接调试接口
- 在 PC 上运行烧录软件(Keil、STM32CubeProgrammer、J-Flash 等)
- 把新固件烧录到芯片 Flash 中
- 装回设备外壳
问题非常明显:
- 设备必须物理接触(拆机、接线),费时费力
- 需要专业人员操作(普通用户无法自己完成)
- 部署在野外的设备(如路灯控制器、农业传感器、基站设备)几乎无法升级
- 每台设备需要逐个升级,批量化几乎不可能
- 用户体感极差("为什么买个门锁还要拆机?")
1.2 IAP(In-Application Programming,在应用编程)
IAP 是一种让程序在运行过程中更新自身的技术。
打个生活化的比方:传统升级就像你要换房子的地基------必须先把房子拆了,再把新地基铺上去,这段时间你无处可住。而 IAP 则像是你住在二楼,一边正常生活,一边让施工队在房子旁边帮你盖一栋新楼,盖好了验收通过后,你再搬过去。
技术原理:
┌──────────────────────────────────────────┐
│ 芯片 Flash 空间 │
│ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Bootloader │ │ 用户程序 (App) │ │
│ │ (不变代码) │ │ (可被替换) │ │
│ │ │ │ │ │
│ │ 负责: │ │ 你的业务逻辑 │ │
│ │ 1.接收新固件 │ │ 正常运行中...... │ │
│ │ 2.验证签名 │ │ │ │
│ │ 3.烧写Flash │ │ 也可以通过接口 │ │
│ │ 4.跳转执行 │ │ 接收新固件数据, │ │
│ └─────────────┘ │ 然后请求Bootloader │ │
│ │ 帮你完成替换 │ │
│ └──────────────────┘ │
└──────────────────────────────────────────┘
IAP 的核心思想只有一句话:Flash 中驻留一段永远不动的代码(Bootloader),它负责接收、验证和写入新的 App 代码,App 可以通过通信接口接收数据并委托 Bootloader 完成自更新。
在 STM32 上,我们通常会把 Bootloader 放在 Flash 的起始地址(比如 0x08000000------复位后 CPU 最先执行的地址),而用户程序放在 Bootloader 之后的一段区域。上电后先跑 Bootloader,由它决定是跳转到已有的 App,还是进入固件更新模式。
IAP 的传输通道可以是多种多样的:
- UART 串口(本项目使用 YMODEM 协议,通过板载 ST-LINK 虚拟串口)
- USB(DFU 模式------Device Firmware Upgrade)
- SPI / I2C(连接外部 Flash 或 SD 卡等存储芯片)
- SD 卡(离线升级方案)
- CAN 总线(汽车电子常用)
- 以太网 / Wi-Fi(进入 OTA 范畴)
- 蓝牙 BLE(穿戴设备常用)
1.3 OTA(Over-The-Air,空中升级)
OTA 是 IAP 的"无线版本"------固件通过无线信道(Wi-Fi、蓝牙、LoRa、NB-IoT、4G/5G)从云端服务器直接传输到设备,无需任何物理接触。
┌─────────┐ ┌─────────┐ ┌──────────────┐
│ 云端服务器│ ═════>│ 网关/App │ ═════>│ IoT 终端设备 │
│(新固件) │ 互联网 │ │ 无线 │ (STM32芯片) │
│ v2.0.sfb │ │(路由器/ │ BLE/WiFi│ │
│ │ │ 手机App) │ │ │
└─────────┘ └─────────┘ └──────────────┘
OTA 是物联网(IoT)的"杀手级功能"。没有 OTA 的 IoT 设备,本质上是一个"一次性交付"的产品------出厂什么样,报废时还是什么样。
1.4 为什么物联网设备必须拥有 OTA 能力?
| 场景 | 没有 OTA 的后果 | 有 OTA 的好处 |
|---|---|---|
| 安全漏洞修复 | 设备变成僵尸网络的一员(Mirai 事件) | 远程推送补丁,数小时内全部修复 |
| 功能迭代 | 硬件召回,成本动辄数十万 | 远程推送新功能,零硬件成本 |
| Bug 修复 | 批量退货、客户投诉、品牌声誉受损 | 静默修复,用户无感知 |
| 协议升级 | 设备无法与新版本服务器通信 | 兼容性持续保持 |
| 商业模型 | 一次性买卖,收入天花板低 | 持续服务收入,SaaS 模式可能 |
现实案例:Mirai 僵尸网络(2016)
攻击者利用数十万台 IoT 设备(网络摄像头、路由器、DVR)的固件漏洞,将其感染为僵尸网络的"肉鸡",对 DNS 服务商 Dyn 发动了史上最大规模的 DDoS 攻击,导致 Twitter、Netflix、Reddit 等网站在美国东海岸大面积瘫痪。
根本原因:这些设备出厂固件留有漏洞,且根本不具备远程更新能力。一旦被感染,唯一的处理方式是物理销毁。
2. 为什么需要"安全"启动?
2.1 不安全启动的风险
如果你的设备上电后直接跳转到 App 代码,不做任何检查,那就像一栋没有门禁系统的大楼------任何人都可以随意进出。
三大核心威胁:
威胁一:恶意固件替换 (Malicious Firmware Replacement)
┌──────────────────────────────────────┐
│ 攻击者通过调试接口或漏洞, │
│ 将 Flash 中的合法程序替换成恶意程序。 │
│ │
│ 正常固件: [智能灯光控制器 v1.0] │
│ ↓ 被攻击者替换 │
│ 恶意固件: [僵尸网络木马 + 挖矿程序] │
│ │
│ 后果:你的设备变成攻击者的工具。 │
└──────────────────────────────────────┘
威胁二:固件篡改 (Firmware Tampering)
┌──────────────────────────────────────┐
│ 攻击者修改固件中的关键参数, │
│ 比如将温度传感器的阈值从80°C调到130°C │
│ │
│ 原始代码: if(temp > 80°C) shutdown();│
│ 篡改代码: if(temp > 130°C) shutdown();│
│ │
│ 后果:设备在130°C才会保护关机------ │
│ 但那时设备可能已经烧毁了。 │
└──────────────────────────────────────┘
威胁三:知识产权盗窃 (IP Theft)
┌──────────────────────────────────────┐
│ 竞争对手通过调试器直接读取 Flash, │
│ 获得二进制文件后进行逆向工程, │
│ 分析出你的核心算法和控制逻辑。 │
│ │
│ 后果:你的产品被快速克隆, │
│ 商业壁垒瞬间消失。 │
└──────────────────────────────────────┘
2.2 真实案例
| 年份 | 事件名称 | 影响规模 |
|---|---|---|
| 2016 | Mirai 僵尸网络 | 感染超过 60 万台 IoT 设备,DDoS 攻击导致美国东海岸大面积断网 |
| 2017 | BrickerBot | 永久性破坏不安全的 IoT 设备,使其"砖化"报废 |
| 2018 | VPNFilter | 感染 50 万台路由器,可远程截获流量、窃取凭证 |
| 2020 | Ripple20 | 影响数亿台设备的 TCP/IP 协议栈漏洞,涵盖医疗、工业、能源 |
| 2021 | PrintNightmare 系列 | 嵌入式打印服务漏洞可被远程利用,工业打印机成攻击入口 |
2.3 STM32 硬件安全基础:RDP、WRP、PCROP
在理解安全启动之前,你需要先认识 STM32 提供的最底层三道硬件安全防线。
2.3.1 RDP(读保护,Read Protection)
RDP 是 STM32 的第一道硬件锁,控制调试接口能否访问 Flash 内容:
| RDP 等级 | Option Bytes 值 | 调试访问 Flash | Flash 内容 | 级别切换 |
|---|---|---|---|---|
| Level 0 | 0xAA |
完全开放(调试器可随意读写) | 明文可见 | →L1 自由切换 |
| Level 1 | 0xBB |
禁止(调试器连接即阻止) | 不可读,但 Flash 仍可执行 | →L0 时触发 Mass Erase(全部擦除) |
| Level 2 | 0xCC |
永久禁止(JTAG/SWD 物理熔断) | 永久锁定 | 不可逆! 芯片永远无法再调试 |
关键安全特性:RDP Level 1 → Level 0 的降级操作会自动触发全片擦除(Mass Erase)。这意味着攻击者无法通过"临时降到 Level 0 → 读出 Flash → 再升回 Level 1"来窃取固件------降级的同时,固件就已经被销毁了。
RDP 等级的安全模型:
Level 0 (开发阶段) Level 1 (生产阶段) Level 2 (极端安全)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 调试器: 自由访问 │ │ 调试器: 禁止访问 │ │ 调试器: 永久禁止 │
│ Flash: 可读写 │ │ Flash: 仅执行 │ │ Flash: 仅执行 │
│ │ │ │ │ │
│ 用途: 开发调试 │ │ 用途: 量产固件保护 │ │ 用途: 军工/支付 │
│ │ ───> │ │ ───> │ │
│ │ │ 降级=L0触发全擦 │ │ 不可逆! 再无法 │
│ │ │ 固件不泄露 ✓ │ │ 调试/烧录/恢复 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
2.3.2 WRP(写保护,Write Protection)
WRP 是区域粒度的 Flash 写保护------它可以锁定指定扇区的写入操作:
- 保护对象 :SBSFU 引导代码区(
0x08006600-0x0800FFFF)、SE 核心代码区 - 作用:即使攻击者通过漏洞获得了代码执行能力,也无法擦除或篡改受 WRP 保护的 Flash 区域
- 粒度:G4 系列为 2KB Flash Page 对齐
- 注意:WRP 只保护"写",不保护"读"(需配合 RDP 和 PCROP)
2.3.3 PCROP(专有代码读保护,Proprietary Code Read Protection)
PCROP 是 STM32 最具特色的一项硬件安全技术:
普通 Flash: PCROP 保护的 Flash:
┌──────────────────────┐ ┌──────────────────────┐
│ CPU 可以: │ │ CPU 可以: │
│ ✓ 执行代码 │ │ ✓ 执行代码 │
│ ✓ 读取数据 │ │ ✗ 读取数据 (硬件阻止) │
│ ✓ 写入数据 │ │ ✗ 写入数据 (WRP+PCROP)│
│ │ │ │
│ 调试器可以: │ │ 调试器可以: │
│ ✓ 读取全部内容 │ │ ✗ 读取 (RDP已锁) │
│ ✓ 修改全部内容 │ │ ✗ 修改 (WRP已锁) │
└──────────────────────┘ └──────────────────────┘
PCROP 允许 CPU 执行 保护区中的代码(取指令),但禁止任何形式的数据读取(数据访问)。这非常适合存放密钥:
- 密钥以
MOVW/MOVT立即数指令的形式嵌入在汇编代码中 - CPU 执行这些指令时可以获取密钥值(加载到寄存器中用于加密运算)
- 但任何尝试通过
LDR指令或调试器读取该地址的企图都会触发硬件错误
三条防线协同工作:
攻击者想要窃取密钥:
尝试 1: 通过调试器读取 Flash
→ RDP Level 1 阻止调试器访问 ✗
尝试 2: 降级 RDP 到 Level 0 再读
→ 降级触发 Mass Erase,密钥随 Flash 一起销毁 ✗
尝试 3: 不降级 RDP,写一段代码把 Flash 内容通过串口发出来
→ PCROP 阻止数据读取,即使用代码也无法读取 ✗
(只有受信任的 SE 代码可以"执行"该区域,但执行 ≠ 读取)
尝试 4: 修改 SBSFU 代码,让它发送密钥
→ WRP 保护 SBSFU 代码区,无法篡改 ✗
尝试 5: 通过 JTAG/SWD 扫描链窃取
→ HDP (硬件调试保护) 在启动后禁用调试接口 ✗
2.4 不同 STM32 系列的安全机制差异
并非所有 STM32 系列都有相同的安全硬件。不同系列采用不同的隔离技术:
| 系列 | 安全隔离技术 | 密钥存储 | 调试保护 | 安全等级 |
|---|---|---|---|---|
| STM32G4 (本项目) | Secure User Memory + MPU | PCROP 保护 | HDP + RDP | ⭐⭐⭐⭐ |
| STM32G0 | Secure User Memory + MPU | PCROP 保护 | HDP + RDP | ⭐⭐⭐ |
| STM32H7 | Secure User Memory + MPU | PCROP 保护 | HDP + RDP | ⭐⭐⭐⭐ |
| STM32L4/L0 | Firewall(防火墙) | Firewall 隔离 | RDP | ⭐⭐⭐ |
| STM32L5/U5 | ARM TrustZone-M | SAU/IDAU 隔离 | TZ-aware 调试 | ⭐⭐⭐⭐⭐ |
| STM32F4/F7 | MPU 软件隔离 | WRP 保护 | RDP | ⭐⭐ |
| STM32WB | CKS + MPU | CKS 加密密钥 | RDP + CKS | ⭐⭐⭐⭐ |
G4 系列的 Secure User Memory 是其区别于 F4 的核心安全升级。它提供 4KB 粒度的安全存储区,可以配合 PCROP 和 MPU 实现多层密钥保护。详细机制将在第 11 篇(PCROP 与 HDP 硬件保护)中展开。
2.5 安全启动的目标
安全启动(Secure Boot)就像给设备装了一套"身份验证门禁系统"------每次有人试图进入大楼,门禁系统都会验证他的身份。
每次上电或复位后,安全启动执行以下流程:
┌──────────────────┐
│ 1. 硬件自检 │ ← 检查芯片安全配置是否正确
│ │ RDP(读保护等级)是否正确?
│ │ PCROP(代码读保护)是否激活?
│ │ WRP(写保护)是否覆盖关键区域?
│ ↓ │
│ 2. 激活动态保护 │ ← 打开 MPU(内存保护单元)
│ │ 打开 HDP(硬件调试保护)
│ ↓ │
│ 3. 固件验签 │ ← 用预置公钥验证用户程序的数字签名
│ │ 签名有效 → 固件来源可信
│ │ 签名无效 → 固件被篡改或伪造
│ ↓ │
│ 4. 完整性校验 │ ← 用哈希值检查固件内容是否被修改
│ │ SHA-384 计算结果 == FwTag?
│ │ 匹配 → 固件完整
│ │ 不匹配 → 固件已被修改(哪怕1位)
│ ↓ │
│ 5. 跳转或拒绝 │ ← 全部验证通过 → 跳转到 App 执行
│ │ 任一验证失败 → 拒绝执行,进入错误处理
└──────────────────┘
核心原则:只运行"经过授权的"、"未被篡改的"代码。这就像公司大楼的门禁------只有持有有效员工卡的员工(经过签名验证的固件)才能进入办公区。
3. 为什么需要"安全"固件更新?
3.1 不安全固件更新的风险
即使设备有安全启动,如果固件更新过程不安全,攻击者照样可以在传输环节动手脚:
风险场景:中间人攻击 (Man-in-the-Middle)
┌──────────┐ ┌──────────────────────┐ ┌──────────┐
│ 官方服务器│ │ 传输通道 │ │ IoT 设备 │
│ (合法固件)│ ════>│ Wi-Fi / 互联网 / 串口 │ ════>│ │
│ v2.0 │ │ │ │ 接受v2.0 │
└──────────┘ └──────────────────────┘ └──────────┘
↑ ↑
攻击者截获固件 │
分析代码逻辑 │ 攻击者冒充服务器
│ 注入假冒固件
┌─────┴─────┐
│ 攻击者 │
│ 伪装固件v2.0│
│ (含后门) │
└───────────┘
3.2 CIA 三要素
安全固件更新需要在三个维度 上提供保护------信息安全领域简称为 CIA 三要素:
| 维度 | 英文 | 核心问题 | 通俗类比 | SBSFU 如何实现 |
|---|---|---|---|---|
| 机密性 | Confidentiality | "别人能看到我的固件吗?" | 给快递上锁 | AES-256-CBC 加密 |
| 完整性 | Integrity | "固件在路上被掉包了吗?" | 给快递贴防拆封条 | SHA-384 哈希校验 |
| 认证性 | Authenticity | "这个固件真的是我发的吗?" | 发件人签名盖章 | ECDSA P-384 数字签名 |
CIA 三要素在 SBSFU 中的协作:
原始固件: [版本号=2.0, 代码区=0x4801..., 配置区=...]
│
├── 加密保护 → AES-256-CBC 机密性 (Confidentiality)
│ 明文 → 密文
│ 即使被截获,攻击者看到的是一堆乱码
│ 就像快递包裹被锁在保险箱里
│
├── 哈希校验 → SHA-384 完整性 (Integrity)
│ 计算固件的384位"指纹"
│ 传输后重新计算 → 对比
│ 哪怕固件中只改了1个bit,指纹就完全不同
│ 就像快递包裹上的防拆封条
│
└── 数字签名 → ECDSA P-384 认证性 (Authenticity)
用只有开发者持有的私钥签名
设备用内置的公钥验证签名
证明"这个固件确实是我们发布的"
就像快递单上盖了发件人的印章
3.3 实际攻击场景对照
| 攻击类型 | 没有安全更新 | 有 SBSFU 安全更新 |
|---|---|---|
| 固件传输中被替换(中间人) | 成功------设备接受假固件 | 失败------签名验证不通过,固件被拒绝 |
| 固件服务器被黑客入侵 | 所有设备收到恶意固件 | 失败------攻击者没有合法私钥,无法生成有效签名 |
| 固件回滚攻击(降级到有漏洞的旧版本) | 成功------旧固件也有"合法"签名历史 | 失败------版本号单调递增检查 + 防回滚指纹机制 |
| 固件内容嗅探(窃取 IP) | 成功------抓包即可获得明文固件 | 失败------固件已使用 AES-256-CBC 加密,窃取后是乱码 |
| 伪造更新通知 | 成功------设备盲目接受 | 失败------签名绑定固件头部元数据,整体不可伪造 |
4. SBSFU 是什么?
4.1 官方定义
SBSFU = Secure Boot + Secure Firmware Update
它是 STMicroelectronics 官方提供的 X-CUBE-SBSFU 软件扩展包,为 STM32 单片机提供一套完整的、可裁剪的、支持多种加密方案的安全启动和安全固件更新解决方案。
你可以把 SBSFU 理解为一个"开箱即用的安全固件管理框架"------ST 已经帮你写好了最复杂的加密操作、状态机管理、Flash 保护和双镜像交换逻辑,你只需要:
- 选择加密方案(本系列使用 ECC384 + AES256 + SHA384)
- 生成密钥对
- 编译三个工程
- 把你的应用逻辑写在 UserApp 中
本系列文章基于以下具体版本和配置:
- SBSFU 版本: v2.6.2
- 目标芯片: STM32G474RE(Cortex-M4, 170MHz, 512KB Flash, 128KB SRAM)
- 开发板: NUCLEO-G474RE
- 加密方案 :
SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384(ECC P-384 + AES-256-CBC + SHA-384) - IDE: Keil MDK-ARM
- 镜像模式: 双镜像(2_Images)
4.2 版本历史简述
| 版本 | 发布时间 | 主要更新 |
|---|---|---|
| v2.0 | 2018 | 初始发布,支持基本的安全启动和固件更新,支持 F4/F7/L4 系列 |
| v2.4 | 2020 | 新增 STM32L5/G0/WB 系列支持,增强双镜像原子交换机制,增加 X.509 证书方案 |
| v2.6 | 2022 | 新增 G4 系列全支持,新增 ECC384/SHA384 高安全强度方案,优化 MPU 多区域隔离,改进 HDP 激活时序 |
4.3 支持的 STM32 系列
- STM32G4 系列(本系列教程使用 NUCLEO-G474RE)
- STM32F4 / F7 / H7 系列
- STM32L0 / L1 / L4 / L4+ / L5 系列
- STM32G0 系列
- STM32WB 系列(无线 MCU)
4.4 关键配置项:SFU_IMAGE_OFFSET
在开始深入了解工程结构之前,有一个重要的配置需要提前了解:SFU_IMAGE_OFFSET。它定义了用户固件镜像在 Flash 槽位中的起始偏移(以字节为单位)。
为什么不同的芯片有不同的偏移?
| MCU 系列 | SFU_IMAGE_OFFSET | 原因 |
|---|---|---|
| STM32F4 / F7 / H7 | 512 字节 | Flash 页大小为 512B(F4)/ 256B(F7),对齐到单页之后 |
| STM32F7 / H7(双 Bank) | 1024 字节 | 双 Bank 配置,需对齐到更大边界 |
| STM32G0 | 2048 字节 (2KB) | G0 最小页大小为 2KB,槽位状态记录需要独立一页 |
| STM32G4(本项目) | 4096 字节 (4KB) | G4 的 Secure User Memory 需要 4KB 扇区粒度,状态记录占整个 4KB 扇区 |
这个偏移量决定了:
- 固件头的存放位置 :在槽位的第
SFU_IMAGE_OFFSET字节处 - 槽位状态记录的粒度:必须能独立地擦除状态区域而不影响固件数据
- Flash 页的磨损均衡:状态记录需要频繁更新(每次下载/安装都更新),必须放在独立页中
在 mapping_fwimg.h 中,这个值被定义为:
c
#define SFU_IMAGE_OFFSET ((uint32_t)4096)
4.5 本项目工程目录结构
2_Images/ ← 双镜像配置的根目录
├── 2_Images_SECoreBin/ ← 安全引擎工程
│ ├── Inc/ ← 头文件
│ │ ├── se_crypto_config.h ← 加密方案选择(核心配置!)
│ │ ├── se_def_metadata.h ← 固件头数据结构定义
│ │ └── se_crypto_bootloader.h ← 加密操作 API
│ └── MDK-ARM/ ← Keil 工程文件
│ └── Project.uvprojx
├── 2_Images_SBSFU/ ← 安全引导程序工程
│ ├── SBSFU/App/ ← SBSFU 源代码
│ │ ├── sfu_boot.c ← 安全启动状态机
│ │ ├── sfu_fwimg_swap.c ← 双镜像交换逻辑
│ │ ├── sfu_loader.c ← 本地加载器(YMODEM 接收)
│ │ └── sfu_mpu_isolation.c ← MPU 隔离配置
│ └── MDK-ARM/ ← Keil 工程文件
│ └── Project.uvprojx
├── 2_Images_UserApp/ ← 用户应用工程(示例)
│ ├── Inc/
│ └── MDK-ARM/
│ └── Project.uvprojx
└── Linker_Common/ ← 链接脚本(定义内存布局)
└── MDK-ARM/
├── mapping_sbsfu.h ← SBSFU/SE 内存映射
└── mapping_fwimg.h ← 固件槽位内存映射
编译顺序严格要求:
SECoreBin → SBSFU → UserApp
(1) (2) (3)
为什么这个顺序?
1. SECoreBin 生成 SE_CORE_Bin 二进制 → 作为 SBSFU 的链接输入
2. SBSFU 链接 SE_CORE_Bin + 自身代码 → 生成完整的 SBSFU 固件
3. UserApp 依赖前两步的产物 → 生成可安全传输的 .sfb 加密固件包
5. 核心概念速览
在深入技术细节之前,你需要先理解这五个核心概念。它们就像一栋安全大楼的不同组成部分:
5.1 信任根(Root of Trust, RoT)
信任根是整个安全体系的基础------它是绝对不可篡改、永远最先执行、无条件可信的那部分代码和数据。在 SBSFU 中,信任根 = 安全引擎(SE)+ 安全引导程序(SBSFU)。
信任链 (Chain of Trust) 模型:
┌──────────────────────────────────────────────────────┐
│ │
│ 信任根 ──────> 安全引导 ──────> 用户程序 │
│ (RoT) (SB) (App) │
│ │
│ 硬件保证 验证 App 被验证通过后 │
│ 不可篡改 签名有效性 才允许运行 │
│ 首先执行 │
│ 绝对可信 │
│ │
│ 如果信任根被破坏 → 整个安全体系崩溃 │
│ 就像身份证印制机器被黑客控制 → 假证就能以假乱真 │
└──────────────────────────────────────────────────────┘
生活化类比:信任根就像是政府颁发的身份证。你信任一张身份证,不是因为它用的是特种纸张或者特殊的印刷工艺,而是因为它的发行机构(公安局)是可信的权威机构。同理,芯片出厂时的硬件安全机制保证了信任根不可篡改------这是整个安全体系的逻辑起点。
5.2 安全启动(Secure Boot, SB)
安全启动是每次系统复位后第一个执行的安全检查流程,它像一道不可绕过的安检门:
┌──────────────────┐
系统复位 →│ 安全启动 (SB) │
或上电 │ │
│ 第一步: 静态检查 │
│ · RDP等级是否正确?│
│ · PCROP是否激活? │
│ · WRP是否配置? │
│ · DBANK是否关闭? │
│ ↓ │
│ 第二步: 动态保护 │
│ · 配置MPU隔离SE区 │
│ · 激活HDP防调试 │
│ ↓ │
│ 第三步: 固件验签 │
│ · 读取Active槽头部│
│ · ECDSA验证签名 │
│ · SHA384校验完整性│
│ ↓ │
│ 第四步: 状态判断 │
│ · State=VALID? │
│ · State=SELFTEST?│
│ ↓ │
│ 第五步: 跳转/拒绝 │
│ · 通过→跳转App │
│ · 失败→进入Loader │
└──────────────────┘
安全启动保证了一条绝对铁律:凡是在芯片上运行的代码,都必须经过验证。没有例外。
5.3 安全固件更新(Secure Firmware Update, SFU)
安全固件更新负责安全地接收、解密、验证和安装新固件。它是安全启动的"配套服务"------光有门禁(安全启动)还不够,你还需要确保新来的"访客"(新固件)是你邀请的那个人。
完整的固件更新流程:
云端/PC (开发者) 设备端 (STM32)
═══════════════ ══════════════
┌──────────────┐ ┌──────────────────┐
│ 编译UserApp │ │ ① SBSFU 启动 │
│ 生成UserApp.bin│ │ ┌──────────────┐ │
│ ↓ │ │ │ 安全启动流程 │ │
│ prepareimage │ │ │ → 进入App │ │
│ .py 脚本 │ │ └──────────────┘ │
│ ↓ │ │ ↓ │
│ 加密+签名+打包 │ │ ② App 运行中 │
│ ↓ │ │ ┌──────────────┐ │
│ UserApp.sfb │ UART/YMODEM │ │ 发现新固件可用 │ │
│ (加密固件包) │ ═══════════════════>│ │ 请求Bootloader│ │
└──────────────┘ │ └──────────────┘ │
│ ↓ │
│ ③ 进入Loader模式 │
│ ┌──────────────┐ │
│ │ YMODEM接收 │ │
│ │ 写入下载槽 │ │
│ │ 解密(AES-256) │ │
│ │ 验签(ECDSA) │ │
│ │ 校验(SHA384) │ │
│ │ 全部通过→交换 │ │
│ └──────────────┘ │
│ ↓ │
│ ④ 下次启动 │
│ ┌──────────────┐ │
│ │ 安全启动→ │ │
│ │ 验证新固件→ │ │
│ │ 跳转新App │ │
│ └──────────────┘ │
└──────────────────┘
5.4 安全引擎(Secure Engine, SE)
安全引擎是 SBSFU 的"保密室"------它垄断了所有加密操作,密钥存放在里面,加密算法在里面执行,外部代码只能通过唯一的入口(CallGate)提交请求。
┌──────────────────────────────────────────┐
│ 安全引擎 (SE) 内部结构 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ CallGate (0x08000204) │ │
│ │ 唯一对外入口 │ │
│ │ 就像银行的柜台窗口: │ │
│ │ - 你可以提交"帮我验证这个签名"的请求 │ │
│ │ - 但你不能走进金库 │ │
│ │ - 也不能看到柜台后面的操作 │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 密钥区 (0x08000400) │ │
│ │ PCROP 硬件保护,CPU可执行但不可读取 │ │
│ │ - ECDSA P-384 公钥 (96字节) │ │
│ │ - AES-256 对称密钥 (32字节) │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 加密算法实现 (0x08000700) │ │
│ │ - ECDSA 签名验证 │ │
│ │ - AES-256-CBC 解密 │ │
│ │ - SHA-384 哈希计算 │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
生活化类比:安全引擎就像是银行的保险库(金库)。你可以在柜台窗口(CallGate)办理各种业务------存款、取款、转账(请求加密/解密/验签服务),但作为客户,你永远进不了金库(看不到密钥),也无法绕过柜台直接接触里面的现金(密钥数据)。金库有防弹玻璃(PCROP保护)、武装警卫(MPU隔离)、24小时监控(硬件监视),层层设防。
5.5 双镜像机制(Dual Image)
双镜像是 SBSFU 实现高可靠性固件更新的核心机制。它的基本思想是:运行和下载分离,两个槽位独立运作。
┌───────────────────────────────────────────┐
│ Flash 空间 (512KB) │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Active Slot (活动槽) 216KB │ │ ← 当前运行的固件
│ │ [SFU1][固件头部][代码+数据] │ │ 设备从这里执行
│ │ 地址: 0x08010000 - 0x08045FFF │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ Swap Area (交换区) 8KB │ │ ← 交换操作的"临时仓库"
│ │ 保证原子性: 要么全完成, 要么全回滚 │ │
│ │ 地址: 0x08046000 - 0x08047FFF │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ Download Slot (下载槽) 216KB │ │ ← 新固件的下载存放区
│ │ [SFU1][固件头部][加密的代码+数据] │ │ App运行时下载到此
│ │ 地址: 0x08048000 - 0x0807DFFF │ │
│ └─────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────┘
双镜像机制的三大优势:
- 运行与下载并行:App 在 Active Slot 正常运行的同时,通过通信接口将新固件下载到 Download Slot。用户完全不会感知到后台正在下载。
- 断电可恢复:即使下载过程中突然断电,下次上电后 SBSFU 可以从断点继续下载(通过固件状态机记录下载进度)。
- 安全回滚:新固件下载完成后,SBSFU 会在下次启动时进行完整的安全验证。如果签名无效或自检不通过,自动回滚到旧固件,保证设备始终有可运行的固件。
6. 单镜像 vs 双镜像
这是 SBSFU 架构设计中最核心的选择。不同的应用场景需要不同的配置。
6.1 全面对比表
| 特性 | 单镜像 (1_Image) | 双镜像 (2_Images, 本项目采用) |
|---|---|---|
| Flash 布局 | SBSFU(64KB) + 1个App区(448KB) | SBSFU(64KB) + Active(216KB) + Swap(8KB) + Download(216KB) |
| 用户程序最大尺寸 | ~448KB | ~216KB |
| 从 App 内发起下载 | 不支持(必须先跳回 Bootloader) | 支持(App 一边运行一边后台下载) |
| 更新期间设备状态 | 停止服务,等待下载完成 | 继续运行,用户无感知 |
| 断电保护 | 直接覆盖原固件,断电即"变砖" | 断电续传,Swap 区保证原子性 |
| 固件回滚 | 不支持 | 支持自动回滚到上一个有效版本 |
| 实现复杂度 | 低(Flash 管理简单) | 中(需要 Swap 区 + 状态机管理) |
| 适用场景 | Flash 空间紧张、不需要无线升级、更新频率极低 | IoT 设备、需要 OTA、对可靠性要求高 |
6.2 单镜像工作原理(为什么不够安全)
单镜像的固件更新流程:
(1) 设备正常运行 App v1.0
┌──────────────────────────┐
│ SBSFU │ App v1.0 │
│ 64KB │ 448KB │
└──────────────────────────┘
(2) 检测到更新 → 跳回 Bootloader → 接收新固件
┌──────────────────────────┐
│ SBSFU │ App v1.0 (被覆盖) │ ← 直接写入 App 区域
│ 64KB │ ████████████████ │
└──────────────────────────┘
↑ 正在擦除+写入中......
(3) 如果此时断电!!!
┌──────────────────────────┐
│ SBSFU │ 半旧半新的垃圾数据 │ ← 设备变砖!
│ 64KB │ │ 没有完整固件可运行
└──────────────────────────┘
单镜像的致命缺陷:下载过程直接覆盖正在运行的 App 区域。如果在覆盖过程中断电,Flash 中既没有完整的旧固件,也没有完整的新固件------设备彻底"变砖"(bricked),只能通过调试器重新烧录。
6.3 双镜像工作原理(本项目采用)
双镜像的安全更新流程(分阶段展示):
═══════════════════════════════════════════════════════
阶段1: 设备正常运行
═══════════════════════════════════════════════════════
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Active槽 │ │ Swap区 │ │ Download槽│
│ [App v1] │ │ [空] │ │ [空] │
│ 正在运行! │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘
═══════════════════════════════════════════════════════
阶段2: App 运行中启动后台下载(不会中断 App 运行!)
═══════════════════════════════════════════════════════
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Active槽 │ │ Swap区 │ │ Download槽│
│ [App v1] │ │ [空] │ │ [接收中] │ ← YMODEM 逐包接收
│ 正常服务! │ │ │ │ 40%完成 │
└──────────┘ └──────────┘ └──────────┘
如果此时断电? → 下次上电,SBSFU 检测到 Download 槽有未完成的下载
→ 可选: 继续下载(续传)或丢弃并启动旧 App
═══════════════════════════════════════════════════════
阶段3: 下载完成,SBSFU 在下次启动时验证并交换
═══════════════════════════════════════════════════════
SBSFU 启动 → 检测到 Download 槽有完整新固件
→ ECDSA 验签 → 通过 ✓
→ SHA384 完整性 → 通过 ✓
→ AES 解密 → 成功 ✓
→ 执行 Swap(使用 Swap 区逐页交换)
交换完成后:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Active槽 │ │ Swap区 │ │ Download槽│
│ [App v2] │ │ [已清空] │ │ [App v1] │
│ 新固件! │ │ │ │ (旧固件) │
└──────────┘ └──────────┘ └──────────┘
如果 Swap 中途断电? → Swap 区记录了交换进度
→ 下次上电后继续完成交换(原子性保证)
═══════════════════════════════════════════════════════
阶段4: SBSFU 验证新 Active 槽固件 → 跳转执行
═══════════════════════════════════════════════════════
SBSFU → 验证 Active 槽 App v2 签名 → 通过
→ 跳转到 App v2 执行
→ App v2 首次运行自检通过 → State 标记为 VALID
为什么只需要 8KB 的 Swap 区?
Swap 区的作用不是存储整个固件镜像,而是逐页交换时的中转站。具体来说:
- 将 Active 槽的一页(2KB)读到 Swap 区
- 将 Download 槽的对应页复制到 Active 槽
- 将 Swap 区的旧数据写回 Download 槽
- 更新交换进度状态
- 重复直到 216KB 全部交换完成
Swap 区只存储当前正在交换的一页(2KB)+ 交换状态记录(在另一个页中),8KB = 4个Flash页足够。
比喻:搬家时你不需要一个和旧房子一样大的仓库------你只需要一辆小货车(Swap区),每次只搬一车东西,搬完一车再回来搬下一车。就算搬家过程中突然下雨(断电),你也能从记录本上看到搬到了第几车,下次继续从那里搬。
Swap Trailer 结构:断电恢复的秘密
Swap 区之所以能在断电后恢复,是因为它的底部存放了一份精密的 Trailer(尾记录)。它类似于数据库的 WAL(Write-Ahead Log,预写日志):
Swap 区底部 Trailer 结构:
┌──────────────────────┐ 高地址 (Swap区末尾)
│ Magic (16 字节) │ ← "SFU_SWAP" 魔数,标识这是一个有效的 Swap 区
├──────────────────────┤
│ Image Resume (16 字节)│ ← 记录当前活跃的镜像信息(FW size、version、offset)
├──────────────────────┤
│ Clean (32 字节) │ ← 填充 0x55,用于检测断电中断后的"脏"状态
├──────────────────────┤
│ Swap State #1 │ ← 槽位 1 的交换状态(逐页记录进度)
│ Swap State #2 │ ← 槽位 2 的交换状态
│ ... │
├──────────────────────┤
│ Swap State #N │ ← 最后一页的状态记录
└──────────────────────┘ 低地址
Clean 字段为什么是 0x55 (0b01010101)?
· 0xFF = Flash 擦除态(全 1),不可用
· 0x00 = 全 0,可能与编程后的"全写 0"混淆
· 0x55 = 交替 0/1 模式,可明确区分为"已编程为 Clean"
→ SBSFU 上电时检查 Clean 字段:
· Clean == 0x55 → 上次 Swap 正常完成,交换区可用
· Clean != 0x55 → 上次 Swap 被中断,需要从 Image Resume 恢复
V1 vs V2 交换算法
SBSFU v2.6.2 提供了两种交换算法。V2 是对 V1 的重要优化:
| 特性 | V1 交换算法 | V2 交换算法 (默认) |
|---|---|---|
| 单页擦写次数 | 3 次 (读→写→擦 旧位置) | 1 次 (直接交换,原位改写) |
| 断电恢复能力 | 支持 | 支持(Clean 标记检测) |
| Flash 寿命影响 | 每次更新多 3 倍磨损 | 每次更新正常磨损 |
| 交换速度 | 较慢(每页多 2 次擦写) | 较(快速每页仅 1 次擦写) |
| 适用芯片 | 所有系列 | G4/G0/H7/L5(推荐) |
V2 算法的核心优化:通过重新设计交换状态机,将每页的"读出→擦除旧位置→写入新位置"三步操作合并为一步直接交换。对于一个 216KB 的固件镜像(约 108 个 2KB Flash 页),V2 每次更新节省约 216 次无必要的 Flash 擦除操作。考虑到 Flash 典型寿命为 10,000 次擦除/页,这显著延长了芯片寿命。
6.4 本项目实际 Flash 地址分配
完整 Flash 内存地图(512KB):
地址 大小 区域名称 说明
──────────────────────────────────────────────────────
0x08000000 ───┐
│ 64KB SBSFU + SE 安全引导 + 安全引擎
0x08010000 ───┤ 64KB边界对齐
│ 216KB Active Slot #1 当前运行的用户固件
0x08046000 ───┤
│ 8KB Swap Area 原子交换临时区
0x08048000 ───┤
│ 216KB Download Slot 新固件下载区
0x0807E000 ───┤
│ 8KB (保留) 未来扩展
0x08080000 ───┘ (512KB Flash 结束)
(第 4 篇文章将对内存布局进行逐字节级别的详细解析。)
7. 本系列文章学习路线图
本系列计划共 15 篇文章,按四个阶段递进,从零基础到可以独立移植和部署。
第一阶段:概念与基础(第 1-4 篇)
| 编号 | 标题 | 内容定位 | 适合谁读 |
|---|---|---|---|
| 01 | 什么是安全启动和安全固件更新(本文) | IAP/OTA 概念、SBSFU 介绍、核心概念速览、单/双镜像对比 | 所有读者,必读 |
| 02 | 环境搭建:Keil MDK 与硬件准备 | 开发板介绍、Keil/CubeProgrammer/TeraTerm/Python 安装、路径问题解决 | 准备动手的读者,必读 |
| 03 | 加密基础:密码学概念速通 | Hash、AES、ECC、ECDSA、数字签名、本项目加密方案全景 | 零密码学基础,必读 |
| 04 | 内存布局:SBSFU 的 Flash 与 RAM 分配 | 精确地址图、MPU/PCROP/HDP 保护机制、每个区域的设计意图 | 想深入理解架构,必读 |
第二阶段:核心原理(第 5-9 篇)
| 编号 | 标题 | 核心内容 |
|---|---|---|
| 05 | 安全引擎(SE)深度解析 | CallGate 调用机制、密钥混淆存储、PCROP 保护原理、SE 加密 API |
| 06 | 固件头部结构(FW Header)详解 | Magic 字段、版本号、FW大小、IV、FwTag、HeaderSignature、ImageState、Fingerprint |
| 07 | SBSFU 状态机(FSM)全解析 | 状态枚举、状态转换图、下载→验证→安装→交换 全流程 |
| 08 | 安全启动流程(Secure Boot)代码走读 | 从 Reset_Handler 到 App main() 的完整代码执行路径 |
| 09 | 安全固件更新流程(SFU)代码走读 | YMODEM 协议接收、流式解密、在线验签、Swap 原子操作 |
第三阶段:保护机制(第 10-12 篇)
| 编号 | 标题 | 核心内容 |
|---|---|---|
| 10 | MPU 内存保护单元配置 | 8个 MPU 区域的分配策略、权限矩阵、上下文切换时的 MPU 重编程 |
| 11 | PCROP 与 HDP 硬件保护 | Option Bytes 配置、PCROP 激活流程、HDP 时序、调试接口管理 |
| 12 | 固件状态与防回滚机制 | State 字段编码(INVALID/VALID/SELFTEST/NEW)、自检测试、版本单调性检查 |
第四阶段:实战与进阶(第 13-15 篇)
| 编号 | 标题 | 核心内容 |
|---|---|---|
| 13 | 编译、烧录与首次运行 | 三步编译实操、STM32CubeProgrammer 烧录、Tera Term YMODEM 传输、终端日志解读 |
| 14 | 密钥生成与固件打包 | KeysAndImages 脚本详解、密钥对生成、固件加密签名、.sfb 文件格式 |
| 15 | 从开发模式到生产模式 | RDP 等级锁定、安全配置固化、量产烧录策略、密钥管理最佳实践 |
学习路径建议
适合所有读者的主线:
═══════════════
① 概念入门 → ② 环境搭建 → ③ 加密基础 → ④ 内存布局
│ │ │ │
└────────────┴────────────┴────────────┘
│
⑤~⑨ 核心原理深度理解
│
⑩~⑫ 保护机制深入掌握
│
⑬~⑮ 动手实战 + 量产准备
- 零基础读者: 严格按 1→2→3→4→5→...→15 顺序阅读
- 有一定 STM32 基础: 可快速浏览①②,重点读③④,深入⑤~⑮
- 只关心"怎么用": ②→⑬→⑭→⑮(快速实操通道)
- 需要移植到其他芯片: 重点读④⑩⑪⑭
小结
本文帮你建立了 SBSFU 的完整认知框架。以下是需要记住的关键点:
- IAP 让程序可以在运行中更新自身,OTA 是 IAP 的无线实现,IoT 设备不可或缺
- 安全启动 = 设备上电后的第一道安检门,确保只有经过授权(有效签名)的固件才能运行
- 安全固件更新 在三个维度保护固件:机密性(AES 加密)、完整性(SHA 哈希)、认证性(ECDSA 签名)
- SBSFU = SB + SFU,ST 官方提供的成熟解决方案,开箱即用
- 信任根(RoT) 是整个安全体系的基石,由硬件保证不可篡改
- 安全引擎(SE) 是密钥和加密算法的"金库",只通过 CallGate 对外服务
- 双镜像机制 是可靠性保证:运行与下载分离、断电可恢复、失败可回滚
- 本项目配置:ECC P-384 + AES-256-CBC + SHA-384,提供 192 位安全强度
一句话总结:SBSFU 就像是给 STM32 穿上了一套防弹衣------别人不能偷看你的固件(加密)、不能篡改你的代码(签名+哈希)、不能注入恶意程序(安全启动)、更新失败也能自动恢复(双镜像+Swap区)。
下一篇:第2篇:环境搭建 ------ Keil MDK 与硬件准备