STM32U5 RDP降级与OEM Key保护机制详解
2026/8/29 18:59:29 网站建设 项目流程

最近调了一块STM32U575的板子,因为产品要防抄板、又要支持售后返修时不丢校准数据,我研究了一下“RDP降级 + OEM Key保护”这套玩法。标题里带的这个词组,“RDP”在MCU语境里不是远程桌面,而是Read-out Protection,读保护;“OEM Key”是ST在U5系列新增的硬件级密钥保护机制;所谓降级,指的是把读保护从Level 1退回到Level 0,让调试器重新能访问Flash。这个笔记把我的试验过程和踩坑点都整理出来了,给正在做U5产品安全设计、或者被“RDP一降级Flash就全空”折磨过的工程师一个参考。

1. 这个笔记解决什么问题

1.1 从一次惨痛经历说起

早年在做F4系列产品的时候,我遇到过这么一件事:客户的一台设备运行异常,需要我远程指导现场的工程师接上ST-Link看一下日志和故障现场。结果现场工程师刚把RDP从Level 1降到Level 0准备读数据,整片Flash全被擦光了。固件没了,校准数据没了,最后只能返厂重刷。那次的根源就是F4等老平台的规则很简单粗暴——RDP Level 1降到Level 0时,芯片内部强制触发全片擦除,没有任何商量余地。这个设计是为了防止有人通过降级来绕过读保护,代价是正规售后也一起遭殃。

STM32U5系列把这个问题解决得比较优雅。它保留了一级降级保护的底线,同时允许你在OTP区域里固化一把OEM Key。只要降级时输入的密钥和芯片内部存储的OEM Key匹配,Flash内容就能原封不动保留,调试器恢复访问。如果不匹配,则照旧全片擦除。这就等于给“售后解锁”开了一条正规门,同时把非法读取的路彻底堵死。

1.2 先把RDP和OEM Key的关系理清楚

RDP在STM32上分为三级,这个基础概念值得再强调一遍:

  • Level 0:没有读保护。调试口随意访问,Flash随便读。
  • Level 1:禁止调试工具通过SWD/JTAG读取Flash,也禁止从RAM或System Memory启动来执行代码读Flash。CPU内核正常运行不受影响。
  • Level 2:最高保护,调试口彻底锁死,选项字节部分永久不可改,没有降级通道。

在Level 1状态下,你还想用调试器调试,唯一的正规退路就是降级到Level 0。老平台降级必然擦片,而U5支持“带OEM Key的降级”,其实就是给降级过程加一道密钥校验:密钥对,Flash保留;密钥不对,Flash清空。ST这样设计的目的也很明确,既保护了厂商的固件不被轻易抄走,又给了正规授权维修一个体面的入口。

1.3 这个特性适合谁、不适合谁

如果你做的是消费类小产品,出厂后基本不回收调试,那OEM Key的意义不大,老老实实开Level 1甚至Level 2就行。但如果你的产品包含以下几种情况,强烈建议研究一下这个方案:

  • 设备内置了传感器校准参数、生产序列号、业务密钥等不可再生数据,售后维修时不能丢;
  • 产品支持现场升级,升级失败后需要恢复调试接口来排查,但不能把整个Flash抹掉重来;
  • 你希望允许售后人员在有授权的情况下打开调试口,同时保证没有密钥的人拿到设备也读不出固件。

U5的定位就是带TrustZone的高安全低功耗MCU,选它做产品基本都会碰到这些安全需求。把OEM Key这个机制吃透,等于给产品上了一道带钥匙的安全门。

2. U5相比老芯片多了什么:OEM Key的核心设计

2.1 老方案为什么会“一刀切”

老平台的RDP降级策略是一个纯二进制的决定:要么保护,要么全擦。从安全角度讲它没毛病,因为如果降级不擦Flash,攻击者就能把Level 1的设备随便降到Level 0然后随便读,那读保护形同虚设。但从工程实践讲,这个策略太粗暴了,它默认所有降级操作都是恶意的,结果就是正常的售后维修也被误伤。

我见过不少团队为了迁就这个机制,干脆不开RDP Level 1,导致固件直接被抄。也见过团队开了保护但维修流程极其痛苦,每次返厂都要重烧固件再贴标定。这两种状态都谈不上好的产品设计。

2.2 OEM Key是怎么工作的

U5系列的OEM Key是一个固化在OTP(One-Time Programmable,一次性可编程)区域的机密。它有这几个特征:

  • 密钥区域属于一次性写入,OTP物理特性决定了只能从1写成0,不可逆,所以Key一旦烧错了,基本只能换芯片;
  • 密钥是全局性的,不是每个用户程序随便改的,它在芯片安全域里被保护起来,用户代码实际上读取不到原始值,防止被程序反读泄露;
  • 它和RDP降级流程硬件联动,由BootROM(芯片出厂固件)在复位流程中完成校验,不是靠用户代码判断。

整个校验过程大致是这样的:芯片处于RDP Level 1,同时使能了OEM Key保护。调试工具发起降级请求,把RDP等级改为Level 0,芯片复位后BootROM发现等级变化,需要产生密钥校验。这时调试工具必须把OEM Key的值提交上来,BootROM把提交值和OTP存储值逐位比对,一致则跳转到正常用户Flash启动,不一致则执行全片擦除。

从用户视角看,降级动作本身和以前一样,就是写Option Bytes后再复位。不同的是多了一步“提交密钥”的过程,CubeProgrammer、ST-LINK Utility或命令行工具都支持这个动作。

2.3 什么时候用多Bank和TrustZone要注意

U5的Option Bytes比老平台复杂得多,除了RDP之外还涉及DBANK(双Bank配置)、SWAP_BANK、PA13/PA14调试引脚复用等。我的建议是,第一次试验不要碰双Bank,就用默认单Bank配置,先把OEM Key这条链路跑通,再去研究Bank切换带来的影响。双Bank模式下芯片的启动行为和非易失性存储操作会有差异,OEM Key校验逻辑也可能跟着Option Bytes的组合变化,数据手册里这些细节写得很细,踩坑成本高,没必要一上来就挑战。

另外,如果同时开启了TrustZone,那调试口、Flash读写、RDP等级还会带上安全/非安全状态的概念,CubeProgrammer里会出现TZEN相关选项。OEM Key本身不依赖TrustZone,但TrustZone一旦启用,很多操作行为和纯非安全模式不一致。我的实验是在不启用TrustZone的情况下完成的,如果你的产品必须开TrustZone,建议把TZEN的调试规则一起捋清楚再动RDP。

3. 动手前准备与关键配置解读

3.1 软硬件环境

我实验用的板子是NUCLEO-U575ZI-Q,这是ST官方评估板,板载ST-LINK,算是U5系列里最容易上手的载体。软件方面用了两个必须的东西:

  • STM32CubeProgrammer(版本建议6.4以上,U5的OEM Key选项在老版本里确实不完整);
  • STM32CubeIDE,用来写测试代码,通过HAL接口读取Option Bytes状态、触发降级复位等逻辑。

你完全可以用软件仿真或者用一块裸板加外部ST-LINK来做,但我不建议在前期用芯片本身内部BootROM做实验,因为一旦OEM Key配置不对,会有概率把芯片搞到无法连接,带板载ST-LINK的评估板至少还能用UR模式抢救一下。

3.2 几个必须提前知道的Option Bytes选项

在CubeProgrammer的OB(Option Bytes)配置界面里,U5会列出大量选项,初学者很容易看懵。和本主题相关的只需要关注这几项:

  • RDP Level:当前读保护等级,可切换Level 0 / Level 1 / Level 2;
  • OBKey0到OBKey7等:OTP中保存OEM Key的寄存器,每个32位,通常8个组合成256位密钥;
  • 与OEM Key相关的锁定开关:把“写入后锁定”的选项打开,防止之后被意外改写;
  • 启动BANK相关配置:保持默认单Bank即可。

还要留意一点,CubeProgrammer把OEM Key存储在OB界面里的位置,不同版本显示的名称有细微差别。我这份实验里它在“OB Key”区域,但换一个版本可能在“OTP”区域下。别慌,按寄存器名字找“OBKey”关键字,或者在界面上用关键字搜“Key”,基本能定位。

3.3 我对当前项目的配置规划

做实验之前我先明确了自己的目标,这很重要,不然很容易随手点设置,结果把板子锁死。

我的目标规划是这样:

  • 出厂时芯片处于RDP Level 1,固件正常跑,但调试口读不了Flash;
  • 烧录一段已知的OEM Key到OTP并锁定;
  • 售后维修时,通过ST-LINK配合正确的OEM Key执行降级到Level 0,Flash内容保留;
  • 密钥错误时,Flash应当全片擦除,确保固件不被窃取。

为了保证实验可复现,我在Flash里预写了几个内容不同的数据块,分别是0xA5、0x5A、0x3C等明显特征值,用来快速判断降级后哪些区域被擦掉了。

4. 完整实操:从烧Key到降级验证

4.1 阶段A:对照组实验,不烧OEM Key直接降级

先从最原始的方式开始,目的是确认老行为在U5上是否还保留。我先把RDP从Level 0切到Level 1,然后用CubeProgrammer的“Read Unprotect”操作执行降级。CubeProgrammer会弹出一条警告,提示“可能擦除Flash”,点击确认后,芯片复位,Flash全空,读取到的内容全是0xFF。

这个结果说明,如果没有OEM Key信息,U5的行为和老平台完全一致:降级即擦除。这一步的意义在于给后面的对照实验打底,我能清晰地判断出“保留Flash”的效果确实是OEM Key带来的,而不是我操作有误。

4.2 阶段B:烧录OEM Key并开启RDP Level 1

接下来是正式实验。先把RDP切回Level 0,保证调试口完全打开,然后在CubeProgrammer的OB界面里找到OBKey相关的寄存器,填入一组测试用的256位密钥,比如我填的是:

OK0 = 0x01234567 OK1 = 0x89ABCDEF OK2 = 0x13579BDF OK3 = 0x2468ACE0 OK4 = 0xFEDCBA98 OK5 = 0x76543210 OK6 = 0x0F1E2D3C OK7 = 0x4B5A6978

注意这把测试Key只是演示用途,真正产品里别用这种顺序规律太强的值。填完以后点写OB,CubeProgrammer会把OBKey写入OTP区域。这里有一个很容易被忽略的步骤,写完之后要把“OBKey锁定”选项一并打开并再次写OB。如果不锁,理论上后续还可以继续改写OTP,安全性打了折扣;锁定之后,这组Key就彻底焊死在芯片上了。

然后我把RDP切到Level 1,同样写OB。此时芯片进入保护模式。为了确认效果,我断开ST-LINK重新连接,尝试读取Flash,读出来的地址全部被屏蔽或报错,这是Level 1的典型表现,说明保护生效了。

4.3 阶段C:三种降级方式逐个验证

接下来才是关键环节。

第一次,我用正确的OEM Key降级。CubeProgrammer里选择“Read Unprotect”操作时,界面上会出现一个输入密钥的区域,把刚才写入的8个32位值逐个填进去,然后执行。复位完成后,我立刻读Flash,之前写的0xA5、0x5A、0x3C这些特征值全部还在。也就是说,密钥匹配时,RDP从Level 1降到Level 0,Flash没有被擦除。

第二次,我用错误的密钥降级,比如把最后一个字改成0x00000000,其他值不变。CubeProgrammer同样执行了降级流程,但复位后再读Flash,所有内容变成0xFF,全片被擦除。

第三次,我做了个更狠的测试:在RDP Level 1下故意让程序跑飞,然后通过ST-LINK的connect under reset模式连接,直接发起降级但不提供密钥。结果是芯片照样擦除了Flash,保护逻辑被强制执行。

这个对照实验很直观地说明了OEM Key的真正作用:它不是阻止降级,而是决定降级后Flash的去留。密钥对了,解锁成功且不丢数据;密钥错了或没有密钥,解锁成功但Flash保不住。

4.4 在用户代码里读取RDP状态

前面几步都是通过CubeProgrammer操作,实际产品中可能需要在设备端做状态检查,比如开机自检时确认当前RDP等级是否符合预期。用STM32CubeU5的HAL库可以直接读,代码非常简单。

#include "stm32u5xx_hal.h" void check_rdp_status(void) { FLASH_OBProgramInitTypeDef sOBCfg; HAL_FLASHEx_OBGetConfig(&sOBCfg); if (sOBCfg.RDPLevel == OB_RDP_LEVEL_0) { // 未开启读保护,注意,调试口可访问 } else if (sOBCfg.RDPLevel == OB_RDP_LEVEL_1) { // 读保护开启,调试口无法访问Flash } else if (sOBCfg.RDPLevel == OB_RDP_LEVEL_2) { // 最高保护,不可回退 } }

这个函数在验证量产固件是否正确烧录时很有用。产品上线前可以在初始化日志里打一次RDP等级,确保产线没有漏设保护。

另外,用户代码也可以通过HAL接口把RDP等级往下降,比如在条件满足的情况下执行固件自更新流程:

void rdp_downgrade_with_key(void) { FLASH_OBProgramInitTypeDef sOBCfg; sOBCfg.OptionType = OPTIONBYTE_RDP; sOBCfg.RDPLevel = OB_RDP_LEVEL_0; HAL_FLASH_Unlock(); HAL_FLASHEx_OBUnlock(); HAL_FLASHEx_OBProgram(&sOBCfg); HAL_FLASHEx_OBLaunch(); }

但注意,代码里无法传入OEM Key,密钥校验仍然是由芯片BootROM在复位后执行,而且用户代码无法直接读取OTP里的Key原文。这个设计是有意的,防止恶意程序把Key读到后通过网络传出。所以固件触发降级时,实际上是触发了一次“需要外部输入密钥”的复位流程,如果你在无密钥工具辅助的设备上直接调用HAL_FLASHEx_OBLaunch,大概率结果就是Flash被擦掉。这一点务必想清楚,不要以为写了代码就等于拿到了密钥。

5. 原理深入:降级时芯片内部发生了什么

5.1 从RDP写入到复位校验的完整流程

把降级过程拆开看,它其实不是“擦除”或“不擦除”这么简单,而是BootROM在执行一个安全状态机。我把我的理解整理成时间线:

  1. 调试工具或用户代码把Option Bytes中的RDP字段从Level 1改为Level 0;
  2. 芯片收到OB Launch命令,触发一次系统复位;
  3. BootROM上电,读取RDP字段和OEM Key相关锁定状态;
  4. 如果检测到“RDP等级正在从高往低变化”,进入密钥校验流程;
  5. 调试工具必须在下一条连接命令中提交OEM Key,BootROM将提交值和OTP中固化的Key逐位比较;
  6. 匹配则跳转到用户Flash执行启动,允许后续调试访问;
  7. 不匹配则执行Flash全片擦除,然后跳转到空Flash状态。

第5步是整个机制的核心。它把“授权降级”从软件逻辑层面搬到了硬件启动流程里,任何绕过应用代码的手段都绕不开这一步,因为这是BootROM的固定逻辑,不是用户代码能改的。这也是我比较认可ST这个设计的地方,它没有把所有安全寄托在应用层,而是放在了芯片最底层。

5.2 为什么OTP一旦烧错Key就只能换芯片

OTP存储器的物理特性是每一位出厂时为1,写入操作只能把1变成0,不能把0还原为1。所以当你发现某一位写错了想要改回来,基本是不可能的。

我这次实验里填Key的时候特别谨慎,就是因为一旦把Key锁定,如果填错任何值,之后无论怎么操作都无法纠正,只能换一块芯片。更恶心的是,锁定之后你可能都没机会验证Key对不对,直到某天售后人员拿着错误Key去降级,Flash直接被擦成空白你才发现问题。所以量产流程里一定要有Key校验环节。

建议的做法是在写Key之前先读一遍OTP的原始值,确认全1,然后再写;写完后立即读回比对。CubeProgrammer支持读回OTP区数据,这是个很好的自检手段。对于大批量生产,最好让产线工具自动生成密钥、写Key、读回比对、再锁Key,一步到位,不要人工手输。

5.3 设计一个产线友好的密钥管理方案

OEM Key在芯片里是固化的,在产线上却是可以生成的,这就带来一个管理问题:每块板子用同一个Key虽然管理简单,但一颗芯片的Key泄露等于全线产品裸奔;每块板子用不同Key安全性高,但产线要维护密钥数据库,售后也要按序列号匹配密钥。

我见过一些团队的做法是:同一批次产品用一个统一的OEM Key,批量烧录时从保密库读取,产线不落地明文,只在烧录工具内存中使用。密钥一旦泄露可以靠固件远程升级换一批新的密钥固件来缓解,但OTP里旧的Key换不掉,所以这种方案只适合对安全性要求不极端的产品。

如果产品安全等级要求高,建议把密钥管理交给HSM(硬件安全模块)或专门的密钥管理系统,出厂时按设备唯一ID生成密钥并写入,同时把密钥副本导入售后系统。这样即使某个Key泄露,影响面也只是单台设备。当然,这样做的成本和复杂度会高不少,具体是否值得要结合产品形态来决定。

6. 常见问题排查与经验

6.1 问题速查表

这段整理了我这次实验和以往项目里常见的问题,不一定全部发生在U5上,但排查思路是通用的。

现象可能原因处理方法
降级后Flash全空,Key没起作用没有正确输入OEM Key,或Key未使能确认Key已写入OTP并锁定,确认在降级时提交了正确密钥
降级后Flash保留,但重启后不能运行FAP/TrustZone或启动Bank配置被意外改动检查Option Bytes里PA13/PA14、SWAP_BANK、双Bank配置,恢复到出厂前值
读保护从L1降到L0后,程序跳飞校准数据区保留但启动地址不对检查代码偏移,确认该降级方式不会修改用户代码区布局
连接ST-LINK报错“Connection error”芯片已处于RDP Level 1或Level 2,调试口受限使用connect under reset模式,或先用UR模式连接后再操作
写OEM Key时发现OTP已有残留值芯片被烧过多次,OTP不可完全擦除只能换芯片,或者接受残留值设计专用密钥
CubeProgrammer无法识别OBKey区域版本过旧,没有U5配置文件升级CubeProgrammer,确保安装包包含U5系列支持
Level 2锁定后还想调试无解,这是设计的终点只能换新芯片,千万别在量产验证时开Level 2

6.2 几条保命经验

第一,永远先备份原始Option Bytes。CubeProgrammer的OB界面里可以直接导出当前OB配置到文件,花十秒钟做一次备份,能在你手滑配置错之后省下大量时间。我现在的习惯是每次调安全相关选项之前先导出一份,等实验做完再对比差异。

第二,实验阶段不要直接上Level 2。Level 1至少还有降级通道,Level 2是真的锁死。很多刚接触RDP的人容易搞混,以为保护等级越高越好,结果把工程板锁成砖。

第三,连接方式建议使用connect under reset。处于Level 1的设备,调试口本来就不允许直接读Flash,但连接阶段如果芯片正在跑自己的代码,ST-LINK和芯片之间可能握手不成功。用复位连接模式,让芯片停在复位状态再发起连接,是成功率最高的方式。

第四,遇到“降级失败”先查密钥锁定状态,不要急着重复操作。有伙伴在烧Key之后没有点锁定位,导致后续的Option Bytes写入把Key区域的一部分改成了其他值,Key已经不正确了,之后再怎么输入原始Key也对不上。这种问题用CubeProgrammer读回OTP区就能发现。

6.3 一块板子被误锁的抢救经历

最后分享一个我踩过的坑。早期测试时我在一块U5板子上开过Level 2,想验证最高保护下到底会不会影响正常启动。验证结果是芯片运行正常,但之后我想用ST-LINK连接看日志,发现SWD口完全没反应。由于Level 2下连Option Bytes都改不了,这块板子彻底沦为“只能跑不能调”的孤岛。最后我只能把它当普通运行设备用,不再尝试调试。后来我把这块板子上的TagAll功能测试完就扔一边了,再也不敢拿它做其他实验。

这个经历教会我一个原则:Level 2只适合最终产品,不适合开发板。开发阶段如果需要保护,用Level 1加OEM Key就足够了,既安全又留了后路。

7. 结语

我在实际调试中最大的感受是,OEM Key并不是一个“加了就安全”的开关,它需要和产品流程配合。Key的生成、烧录、保管、售后使用,每一环都要有清晰规范,否则要么Key泄露形同虚设,要么Key遗失售后束手无策。我建议你把这块板子的Key烧录流程写成脚本,固化到产线工具里,并且建立一把独立的测试Key用于研发调试,和量产Key分开管理。这样研发和售后各用各的权限,互不影响。如果你也在做U5相关的安全设计,欢迎一起交流折腾经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询