news 2026/8/29 3:02:11

STM32安全启动与固件更新实战:从RDP保护到SBSFU

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32安全启动与固件更新实战:从RDP保护到SBSFU

做嵌入式开发这些年,“安全启动”这个词越来越躲不开了。尤其产品一旦走到量产、要走OTA,客户第一句话可能就是:固件被人读出来怎么办?别人能不能刷个第三方包进去?设备被篡改后会不会变成攻击跳板?这些问题不是危言耸听,我在一个表计项目里就亲眼见过,升级口暴露在外,测试人员误发了一包损坏固件,Bootloader连校验都没有直接跳转,设备当场死机,返工一片。所以STM32上的安全启动和固件更新,不是“要不要做”的问题,而是“怎么做才不出事”的问题。

这篇文章我从实际开发角度,把安全启动的底层逻辑、RDP保护、密钥管理、SBSFU参考实现、固件更新状态机和回滚保护完整过一遍。适合正在做量产固件、准备做OTA、或者想从普通Bootloader升级到安全方案的开发者参考。内容不追求教科书式严谨,只讲我在板子上真实跑通过、也踩过坑的东西。

1. 为什么STM32要加安全启动

1.1 裸奔Bootloader的四个致命伤

大多数项目里的Bootloader长什么样?上电检查某个Flash地址有没有数据,CRC算一下,没问题就跳转App,完事。这套方案在样机阶段完全够用,但放到量产和公网环境,问题非常明显。

第一,没有真实性校验。CRC只能保证数据在传输中没被改错,它防不了“有人故意构造一份合法CRC的坏固件”。攻击者不需要读懂你的代码,只需要把你的固件包抓下来改几个字节,重新算一遍CRC刷进去,Bootloader照样放行。

第二,没有保密性。很多人不知道,STM32默认状态下调试口是完全开放的,SWD连上就能把整个Flash读出来。你的算法、通信协议、服务器地址全在里边。就算烧录时没开调试口,只要RDP等级是Level 0,拿到板子就等于拿到源码。

第三,没有防回滚机制。攻击者先记录一个旧版本固件包,等你修复了漏洞、升级了高版本之后,他把旧版本刷回去,让设备停留在有漏洞的状态。这在安全更新里叫降级攻击,普通Bootloader完全没有概念。

第四,升级过程不可控。在传输到一半断电、传错分区、两个Bank都写脏的情况下,设备只能变砖。没有恢复模式和故障转移机制,售后成本很高。

1.2 安全启动到底解决了什么

安全启动的核心思路很简单:芯片上电后,先执行一段不可被篡改的信任根代码,由它对应用程序做签名校验和完整性校验,全部通过才跳转执行。这样即使App被改过、被替换成非官方固件、被降级到旧版,都会被拦在启动门外。

固件更新则是在这个基础上,解决“新固件怎么安全进来”的问题。传输前加密防窃听,写入前验签防伪造,更新失败能回退防变砖。总结起来,一套完整方案要覆盖四件事:固件保密性、完整性、真实性、更新过程安全性

我在项目里给客户做汇报时,经常用一个类比:普通Bootloader等于小区单元门,谁都能进;安全启动等于电梯刷卡闸机,不仅要刷卡,还要核对你是不是业主、你去的楼层是不是你家。虽然每次回家都多一步,但丢东西的概率低太多了。

2. 打好信任地基:RDP、密钥与芯片唯一ID

2.1 信任根落在哪

安全启动的前提,是有一段代码从一开始就值得信任,而且没人能改。在STM32上,这段代码的存放位置通常有两个选择:一是芯片BootROM里固化的出厂引导程序,二是你烧进内部Flash、写保护后锁死的SBSFU区。

前者由芯片原厂维护,你无法改动,适合做最底层的信任锚;后者完全由你控制,适合放校验逻辑和应用策略。SBSFU参考实现用的就是第二种方案:用户应用区写保护,Boot区运行时不开放写权限,这样即使App被攻破,也改写不了Boot区代码。

这里有个容易忽视的点:信任根的“不可篡改”属性必须硬件配合。如果只是代码层面加个判断,攻击者直接通过调试接口改Flash,一切白搭。所以后面要提到RDP和Option Bytes,它们才是把“不可篡改”落到实处的关键。

2.2 RDP读保护等级对照

STM32的RDP(Read-out Protection)分为三级,每一级的安全强度差别很大,量产前必须想清楚。我做了个对照表,方便直接参考:

RDP等级调试访问Flash读取能否回退适用场景
Level 0完全开放允许读取无需回退开发调试阶段
Level 1禁止访问Flash禁止读取可回退到Level 0,但会全片擦除大多数量产产品
Level 2完全禁用调试口禁止一切外部访问不可回退,永久锁定安全要求极高的产品

Level 1是性价比最高的量产选择。调试口连不上,Flash读不出来,但如果你需要返厂维修,还可以降级重刷,代价是全片擦除,代码和数据都会消失。

Level 2就真的要慎重了,一旦锁死,连官方工具都救不回来,芯片基本等于报废。我在一个门锁项目里为了“更安全”选了Level 2,结果后来客户要求加一个功能,只能整片换芯片,那个月被生产部门骂惨了。

2.3 密钥体系与芯片UID的用法

安全启动的校验强度,最终落在密钥上。常见做法是非对称签名 + 对称加密组合:ECDSA P-256做固件签名校验,AES-GCM做固件数据加密。私钥保存在自己的服务器或HSM里,公钥在首次量产时烧进芯片OTP区域。

这时候就轮到芯片唯一ID上场了。STM32每一颗芯片都有96位唯一ID,不同型号地址不同,但都出厂烧死不可改。这个ID不只是查询用,它可以把你的安全方案从“所有设备一把密钥”升级成“一机一密”。做法是:服务器端用设备UID派生每个设备的固件加密密钥,固件包只对该设备有效,其他设备拿到也用不了。就算某台设备的密钥被逆向出来,也无法影响整个产品线。

我建议在产品设计早期就把UID读出来放在固件头里做绑定校验。等到量产再想加,要么返工改Bootloader,要么增加产线工序,成本完全不同。

3. 一步一步把安全启动跑起来

3.1 方案选型:官方SBSFU还是自研Bootloader

实现安全启动有两条路:直接用ST官方SBSFU,或者自己写一套。我两条路都走过,说点实在的。

SBSFU是STM32CubeFW包里提供的参考实现,全称Secure Boot and Secure Firmware Update。它已经帮你写好了ECDSA验签、AES-GCM加解密、多固件分区管理、更新状态机、回滚保护这些麻烦事,你要做的大多是配置和适配。我的建议是:产品阶段优先用SBSFU改造,学习阶段自己写一遍

自己写的好处是彻底搞懂原理,坏处是密码学这种东西,自己实现太容易埋雷。比如签名校验里有一个字节没比对到位,或者缓冲区溢出了,外部攻击者可能就利用这个漏洞绕过校验。我见过一个团队自研验签逻辑,把签名数据copy到局部变量里再比较,编译器优化后直接跳过了部分校验,量产固件被人在网上发了破解教程。

不过SBSFU也有门槛:代码结构复杂,CubeIDE工程打开后文件树几十个文件,第一次看容易晕。所以不要急着改逻辑,先跑通官方demo,再一对一地调。

3.2 跑通官方SBSFU:编译、烧录与首次安全启动

我以NUCLEO-L4R5ZI开发板为例,实际操作步骤如下。

第一步,准备软件。ST官方推荐用STM32CubeIDE,加上STM32CubeProgrammer烧录工具。CubeFW_L4固件包里自带SBSFU例程,路径在Projects/NUCLEO-L4R5ZI/Applications/SBSFU

第二步,编译两个工程。SBSFU例程分了多个子工程,核心是两个:一个是SBSFU本身,生成Boot固件;一个是UserApp,也就是被保护的应用程序。两个工程要分别编译,注意UserApp的链接脚本已经在分区表里定位好了,编译出来直接落在规定的Flash地址上。

第三步,烧录和首次安全启动。这一步和烧普通固件不一样,不能用ST-LINK直接把UserApp拖进去就完事。SBSFU有专门的首次安装流程,需要在STM32CubeProgrammer里选择Secure Firmware Install模式,按照步骤先注入公钥到OTP,再烧录Boot固件,最后烧录已经签名加密的UserApp镜像。

我强烈建议第一次跑通之前,不要跳过SFI流程手动改烧录顺序。一旦公钥没有正确注入OTP,或者UserApp没有经过签名加密,SBSFU启动时会直接停在“验证失败”的状态,串口上只会打印一行Error,很容易让人误以为是代码编译错。

第四步,观察串口日志。SBSFU默认会输出启动日志,正常流程能看到它完成签名校验、跳到UserApp,UserApp再打印自己的日志。到这一步,你的安全启动链路就算闭环了。

如果你的板子不是官方开发板,移植时重点关注三处改动:Flash分区地址(SBSFU区、UserApp区、暂存区各占多少)、Option Bytes配置(哪些区域写保护)、以及跳转时的向量表偏移。向量表偏移分两步,一个是链接脚本里FLASH_APP_OFFSET,一个是代码里SCB->VTOR = APP_BASE_ADDRESS,少一个App都会跑飞。

3.3 手写最小安全Bootloader的核心框架

如果你想彻底搞懂,强烈建议自己写一版最简化实现。不需要做加密,只需要做到:上电读App头,验签,通过就跳转。

核心伪代码大概是这样:

#define APP_BASE_ADDRESS 0x08008000 #define SIG_OFFSET 0x40 static void jump_to_app(void) { uint32_t app_sp = *(volatile uint32_t *)APP_BASE_ADDRESS; uint32_t app_pc = *(volatile uint32_t *)(APP_BASE_ADDRESS + 4); // 关闭中断、清理外设时钟 __disable_irq(); SysTick->CTRL = 0; // 设置向量表偏移 SCB->VTOR = APP_BASE_ADDRESS; // 切换到App栈指针并跳转 __set_MSP(app_sp); ((void (*)(void))app_pc)(); }

验签部分用mbedTLS提供的ECDSA函数,公钥从OTP读出,签名和App版本一起放在App头部。这几个要点容易踩坑:验签函数返回后必须显式检查返回值,不能只调用不判断;Bootloader代码不要引用UserApp的库函数,否则链接时地址错乱;跳转前要恢复默认的中断优先级、关掉非必要外设时钟,否则App初始化时可能HardFault。

我自己手写这版的时候,第一次跳转后App直接死机,查了半天发现是Bootloader初始化了串口,但跳转前没关串口中断,App启动时中断来了,而复位向量已经切走,找不到处理函数。从那以后我习惯在跳转函数里把所有外设中断全禁用,再统一清一次NVIC。

4. 固件更新全流程:打包、签名校验、回滚保护

4.1 固件更新包的结构与处理流程

有了安全启动,固件更新才能安全落地。一套完整的固件更新流程,不只是“把新固件放进Flash”,它是一整个状态机。

固件包结构我一般这样设计:

字段长度说明
Magic4字节固定值,如0xA5A5A5A5,用于识别合法镜像
版本号4字节单调递增,用于防止降级攻击
镜像长度4字节加密后固件长度
IV/Nonce16字节AES-GCM初始化向量
签名64字节ECDSA P-256签名,覆盖前面所有字段和固件内容
固件密文可变AES-GCM加密后的App二进制

接收端拿到固件包后,按顺序执行:先校验Magic和版本号,再验签名,签名通过才解密写入暂存区,全部写完后重启,由SBSFU再次完整校验,通过后切换到新固件。这个流程里每一步失败都要有明确的结果码,不能静默跳过。

我见过不少项目把版本号仅仅当作显示用途,升级时根本不比较,结果攻击者可以把设备刷回任意旧版本,然后利用旧版本漏洞控制设备。版本号比较必须放在接收端、由Bootloader或安全引导代码执行,不能只靠上位机自觉。

4.2 双Bank升级与版本回滚保护

升级最怕的是什么?不是失败,而是失败之后设备变砖。双Bank设计是解决这个问题的常见方案。

双Bank的意思是把应用Flash分成两个区:当前运行区(Bank A)和备用区(Bank B)。新固件写入Bank B时,Bank A照常运行,不打断业务。写入完成并校验通过后,把启动标志翻转,下次开机从Bank B启动。如果新固件启动失败,Bootloader检测到异常,还能自动切回Bank A,完成回滚。

这个方案要求Flash容量大概是应用固件大小的两倍。不少STM32型号从硬件上支持双Bank,比如读Bank B时也可以从Bank A执行,不需要额外搬运。如果你的Flash容量紧张,退而求其次用“暂存区 + 搬移”方案,也有,但升级过程多一个拷贝步骤,失败概率更高。

回滚保护则要分两层来看。一层是防降级攻击,版本号单调递增;另一层是业务连续性,新版本起不来要能回退到旧版本。这两个目标需要同时满足,实际设计时我做了一个简单的启动计数器和看门狗配合机制:新版本启动后在规定时间内上报一次“我已经正常运行”,如果超时没上报,Bootloader判定启动失败,自动回滚到上一个稳定版本。

4.3 更新通道与传输可靠性

更新通道的选择取决于产品形态。消费类设备常用UART或USB DFU,联网设备走HTTP或HTTPS下载,低功耗物联网设备走LoRa、NB-IoT之类的窄带通道。

STM32上做HTTP客户端,方案很多。轻量级可以用LwIP自带的httpd或raw API自己写解析,复杂一点也可以在LwIP上移植cURL。需要留意的是,HTTP只是传输层,固件本身必须加密签名,否则中间人抓包就能拿到你的固件。如果通道支持TLS,优先用TLS,它能解决传输保密和通道完整性。如果窄带设备没法跑TLS,那就回到固件级的AES-GCM加密,保证每一包数据即使被截获也无法解密和篡改。

UART通道升级时,我建议用YModem协议加接收超时处理。STM32的UART接收用DMA加空闲中断,能更高效地处理不定长数据。我在一个项目里遇到过一包数据被粘包、一个包被拆成两段的问题,后来改成“帧头+长度+CRC”的自定义协议,逐包确认回复,才把传输成功率提上来。

不管什么通道,都必须做断点续传或失败重传机制。设备端接收固件时,要记录已经写入的块号,中断后从断点继续,而不是从头再来。否则几兆的固件在弱网环境里,永远升不完。

5. 常见问题与排查技巧实录

5.1 上电启动失败:先从这六点查

SBSFU或自研Bootloader加好之后,最容易遇到的问题就是上电不启动、没有任何日志,或者跳转到App后马上死机。我把排查思路整理成了列表,照着查基本能定位。

第一,先确认SBSFU有没有跑起来。在Boot区加一个GPIO翻转,用示波器看电平变化,比盲猜快得多。有日志口的直接从日志口看。

第二,检查公钥有没有正确烧进OTP。很多人改了自己的密钥,却在SFI流程里用了默认密钥,验签当然失败。

第三,检查Flash分区地址。SBSFU区的链接脚本、UserApp的链接脚本、烧录地址三者必须严格一致。地址错一位,跳转就全乱。

第四,确认向量表偏移。App工程里不是只改链接脚本就行,代码里要设置SCB->VTOR,否则中断向量表还在Boot区,App一进中断就崩。

第五,跳转前有没有把外设恢复默认。Bootloader里初始化了时钟、串口、DMA,跳转前得停掉并清理NVIC挂起中断,否则App初始化时会撞上残余状态。

第六,确认RDP等级没有被意外提升。如果调试器连不上,先试试Options Bytes能不能读;不能读又修改不了,多半是Level 2了。

5.2 固件更新失败:签名、解密与升级状态机

固件更新失败的问题往往集中在签名校验不过、解密乱码、更新后重启又回到旧版本这三类。

签名校验不过,90%是密钥不匹配。上位机签名用的私钥和芯片里存的公钥不是同一对。我之前自己生成密钥时,不小心把测试密钥当成了生产密钥签发固件,结果线上设备全部拒绝升级,还好只是测试环境。建议密钥生成后立刻给ID做标签,并且生产密钥和测试密钥严格分离。

解密乱码,先查块对齐。AES-GCM是分组密码,固件加密时如果最后一块没有补齐到16字节,解密端算出来的认证标签就会对不上。很多上层的“解密失败”,实际上不是密码算错了,而是固件长度没有按块对齐。

更新后回到旧版本,要查启动标志在哪里翻转、什么时候翻转。如果新固件没有运行到“确认正常”的函数,Bootloader会在下次上电时认为新固件不可用,自动回滚。这是设计行为,不算Bug。但如果你希望新固件启动失败时能保留用于调试的日志,建议在回滚前把异常原因保存到一段独立的保留区。

5.3 调试工具与生产环节的坑

工具选择上,STM32 ST-LINK Utility确实是老工具了,官方早已停止更新,新项目就统一用STM32CubeProgrammer。它支持SFI安全烧录、RDP等级设置、Option Bytes读写、外部Flash编程,一个工具全搞定。

调试口连不上的情况,最常见的是烧录时Option Bytes把SWD关了,或者RDP等级被升到Level 1以上。遇到这种问题,先在CubeProgrammer里勾选“Connect under reset”再重试,多数Level 1的情况都能救回来。如果是Level 2,说实话除了换芯片没有太好的办法,所以生产流程里一定要有防误操作的检查项。

生产环节还有一个容易被忽略的问题:密钥怎么注入产线。如果直接在产线电脑上保存生成密钥用的私钥,等于把整个产品线的安全拱手让出。建议量产时采用SFI流程,通过STM32CubeProgrammer的Secure Programming功能把公钥烧进OTP,同时私钥只存在于离线的签名服务器上,不进入产线网络。

调试阶段还有一个技巧:SBSFU支持打开调试日志,输出每一步校验结果。把DBG_TRACE开关打开之后,启动卡在哪一步一目了然,比我用示波器一个一个引脚查效率高太多。出了问题先开日志,再动手改代码。

6. 个人经验与后续建议

做了几个安全启动相关的项目之后,我最大的体会是:技术实现只是其中一半成本,更重的是密钥管理和升级策略的设计。代码写错了可以改,密钥泄露了整个产品线都得重来。所以哪怕是小项目,也建议至少做到私有密钥离线保存、双人交叉备份、产线不接触私钥这三条底线。

另外,不要一上来就奔着“绝对安全”去,安全是分级的。根据产品价值选合适方案,比如消费级设备做到Level 1加签名校验就够,对安全性要求高的安防、金融支付设备才需要Level 2加一机一密。把攻击成本拉高到大于产品本身的价值,就已经达到了商用量产的目标。

最后分享一个小技巧:如果你准备在项目里引入安全启动,不要等到产品功能全部做完了再加,那时候大部分Flash布局和启动流程都定了,改动成本极高。最好从原型阶段就把Boot分区、App分区、暂存区、保留区的地址画出来,哪怕第一版只做最简单的CRC校验,也要把分区结构固定下来。后面升级到签名校验、加密升级、双Bank,只需要在已有框架上加逻辑,不用推倒重来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 3:00:36

计算机单片机毕设实战-基于 STM32 的智能体重身高测量与 BMI 计算装置设计 基于 STM32 单片机的健康体征采集及语音播报平台设计(013705)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/29 3:00:29

计算机单片机毕设实战-基于 STM32 的 OLED 环境数据显示与远程智能控制系统开发 基于 STM32 的烟雾温度光照监测与设备联动控制系统设计(013905)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/29 3:00:13

AI时代新高端职业:编写技能而非制作幻灯片

“新高端职业:编写技能而非制作幻灯片”这句话,我第一次看到时觉得有点像是在制造概念焦虑。但后来仔细观察自己和周围人的工作方式,发现它戳中的不是表面上的“幻灯片该不该做”,而是整个职场人在AI时代重新分工的问题&#xff1…

作者头像 李华
网站建设 2026/8/29 2:57:55

YOLOv10n+PaddleOCR v2.7车牌识别Windows本地部署实战

简介:车牌识别是计算机视觉在智能交通中的基础应用,其核心在于目标检测与OCR文本识别的协同优化。原理上需兼顾模型轻量化、CPU推理效率与中文字符鲁棒性,技术价值体现在低资源设备(如i5笔记本、工控机)上的高精度、低…

作者头像 李华
网站建设 2026/8/29 2:57:37

AI与类器官结合:从数据瓶颈到活体计算的新技术路径

“AI Is Dead. Organoids Are Alive”——如果你最近刷技术新闻,一定见过这类标题。它表面是 AI 讣告,实际却指向一个正在发生的判断:靠堆算力、堆参数、堆数据的旧有 AI 发展模式,正在进入边际收益递减区;而把“活的细…

作者头像 李华
网站建设 2026/8/29 2:57:32

STM32定时器中断:从标准库到HAL库的配置与实战

1. 从“轮询”到“中断”:为什么定时器是STM32的基石如果你刚开始玩STM32,可能还在用HAL_Delay或者自己写个for循环来“数时间”。这没问题,就像学走路,先得会爬。但当你开始做稍微复杂点的东西,比如让一个LED精确地每…

作者头像 李华