2、STM32安全架构:安全特性总览

各位同学,今天我们来聊聊STM32的安全架构。说实话,我刚接触这个领域时,也被一堆缩写搞得头晕——TrustZone、OTP、RDP、PCROP……这些到底都是干嘛的?别急,我们一个一个拆开看。

我个人习惯把STM32的安全特性分成两大类:硬件隔离类存储保护类。前者负责“不让坏人进来”,后者负责“不让数据跑掉”。你想想看,嵌入式设备的安全,说白了就是这两件事。

2.1 TrustZone:硬件级别的“隔离墙”

TrustZone是ARMv8-M架构引入的硬件安全扩展。嗯,这里要注意,不是所有STM32都有这个功能。目前主要是STM32L5、STM32U5、STM32H5这些较新的系列才支持。

它的核心思想很简单:把芯片分成两个世界——安全世界(Secure World)和非安全世界(Non-Secure World)。

关键点:TrustZone不是软件模拟的,是硬件强制隔离的。这意味着即使非安全世界的代码跑飞了,也碰不到安全世界的数据。

我在项目中遇到过一件事:有个客户的产品需要同时运行RTOS和加密算法。RTOS本身漏洞多,万一被攻击了怎么办?我们用TrustZone把加密密钥和算法放在安全世界,RTOS跑在非安全世界。这样一来,就算RTOS被攻破,攻击者也拿不到密钥。

具体怎么配置?看代码:

/* 配置SAU(安全属性单元) */
SAU->RNR = 0;                    // 选择区域0
SAU->RBAR = 0x0C000000;          // 安全世界起始地址
SAU->RLAR = 0x0C01FFFF;          // 安全世界结束地址
SAU->RLAR |= SAU_RLAR_ENABLE_Msk; // 使能该区域

/* 配置MPU(内存保护单元) */
MPU->RNR = 0;
MPU->RBAR = 0x0C000000;
MPU->RASR = MPU_RASR_AP_RW_RW |   // 非安全世界可读可写
            MPU_RASR_SIZE_0x20000; // 128KB区域

说白了,SAU决定哪个地址属于哪个世界,MPU决定谁能访问。这两个配合起来,就是TrustZone的“看门人”。

我的建议:刚开始用TrustZone时,别贪多。先只保护最关键的数据(比如密钥、证书),等熟悉了再扩大保护范围。

2.2 OTP:一次编程,终身不变

OTP(One-Time Programmable)就是“一次编程”的意思。你写进去的数据,以后再也改不了了。为什么会需要这个?

举个例子:你的产品出厂时烧录了一个唯一ID,或者一个根证书。如果这个数据能被篡改,那整个安全体系就崩塌了。OTP就是用来干这个的——写进去,锁死,谁也改不了。

STM32的OTP通常有几百字节到几KB的空间。我记得在STM32F4系列上,OTP有512字节,分成16个块,每块32字节。每个块可以单独锁定。

/* 写入OTP示例 */
#define OTP_START_ADDR  0x1FFF7800

void OTP_Write(uint32_t offset, uint32_t data) {
    uint32_t *p = (uint32_t *)(OTP_START_ADDR + offset);
    *p = data;  // 写入数据
    
    /* 锁定该OTP块,防止再次写入 */
    HAL_FLASHEx_OTP_Program(FLASH_OTP_WRLOCK, offset / 4, 0);
}

注意:OTP一旦锁定,就真的不能解锁了。我曾经有个同事,调试时不小心把OTP锁了,结果芯片只能报废。所以,量产前一定要确认数据没问题。

2.3 RDP:读保护,防的就是“读”

RDP(Read Protection)是STM32最基础的安全特性。它的作用很简单:防止别人通过调试接口(SWD/JTAG)读取你的Flash内容

RDP有三个级别:

级别 名称 说明
Level 0 无保护 调试接口完全开放,谁都能读Flash
Level 1 中等保护 调试接口受限,但可以通过全擦除来解除
Level 2 高级保护 调试接口永久关闭,无法恢复

我个人建议:量产产品至少用Level 1。Level 2虽然更安全,但一旦出问题,芯片就废了。你想想看,如果产品需要返厂升级,Level 2的芯片连调试都进不去,多尴尬。

配置RDP也很简单:

/* 设置RDP为Level 1 */
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&OBInit);  // OBInit中设置RDPLevel为OB_RDP_LEVEL_1
HAL_FLASH_OB_Launch();           // 生效

避坑指南:我曾经遇到过一批芯片,RDP Level 1设置后,发现SWD还能读Flash。后来查了半天,原来是Option Bytes没正确写入。记住:设置RDP后,一定要调用HAL_FLASH_OB_Launch(),否则不生效。

2.4 PCROP:保护你的“核心代码”

PCROP(Proprietary Code Readout Protection)是STM32的一个“狠角色”。它允许你指定某段Flash区域,任何人都不能读——包括你自己。

你可能会问:我自己都不能读,那怎么用?

答案是:CPU可以执行这段代码,但不能把它当成数据读出来。说白了,PCROP保护的是“代码”,而不是“数据”。

这有什么用?举个例子:你的产品里有一段核心算法,比如加密算法或者电机控制算法。你不想让别人逆向工程,就可以把它放在PCROP区域。别人即使拿到了芯片,也读不出这段代码。

/* 配置PCROP区域 */
#define PCROP_START_ADDR  0x08020000
#define PCROP_END_ADDR    0x0802FFFF

FLASH_OBProgramInitTypeDef OBInit;
OBInit.OptionType = OPTIONBYTE_PCROP;
OBInit.PCROPStartAddr = PCROP_START_ADDR;
OBInit.PCROPEndAddr = PCROP_END_ADDR;
OBInit.PCROPConfig = FLASH_PCROP_CONFIG_ZONE_A;

HAL_FLASHEx_OBProgram(&OBInit);
HAL_FLASH_OB_Launch();

重要提醒:PCROP和RDP是两回事。RDP保护整个Flash不被读,PCROP只保护指定区域。而且,PCROP区域内的代码不能自修改,否则会触发HardFault。我刚开始用的时候,就因为这个bug排查了两天。

2.5 其他安全特性一览

除了上面四个,STM32还有一些“小但有用”的安全特性:

  • HDP(Hardware Debug Protection):硬件调试保护,防止调试器在特定时刻介入。我在调试Bootloader时用过,效果不错。
  • WRP(Write Protection):写保护,防止Flash被意外擦写。适合保护Bootloader区域。
  • Secure Boot:安全启动,确保芯片只运行经过签名的固件。这个我们后面章节会详细讲。
  • TRNG(True Random Number Generator):真随机数发生器,用于生成密钥。别用伪随机数,那玩意儿不安全。

2.6 如何选择?

这么多安全特性,到底用哪个?我个人的经验是:

  1. 普通产品:RDP Level 1 + OTP存储唯一ID,足够了。
  2. 需要保护算法的产品:加上PCROP,把核心算法藏起来。
  3. 高安全需求产品:上TrustZone,把安全和非安全世界彻底隔离。
  4. 金融/物联网设备:全套上——TrustZone + PCROP + RDP Level 2 + Secure Boot。

记住一句话:安全没有银弹。没有哪个特性能解决所有问题。你需要根据产品的威胁模型,选择合适的组合。

总结一下:TrustZone是“隔离”,OTP是“固化”,RDP是“防读”,PCROP是“防抄”。这四个特性配合使用,基本能覆盖大部分嵌入式安全需求。

下一章,我们会深入TrustZone的配置细节。到时候我会带大家一步步搭建一个安全世界和非安全世界的通信机制。嗯,那才是真正有意思的部分。