
STM32G0这个系列我前前后后做了好几个项目从G071的电机控制到G0B1的数据采集终端说不上精通但踩过的坑绝对不少。以前我一直有个刻板印象G0就是一颗走量用的Cortex-M0小芯片谈安全那是L5、H7那些带TrustZone的“大芯片”才需要考虑的事。直到有一次我把一块量产板借给客户做联调对方工程师拿着ST-Link随手一接十几分钟就把整片Flash用CubeProgrammer拽了出来连我写在代码注释里的服务器地址都看得清清楚楚。那一刻我才意识到没有人会因为你的芯片便宜就放弃研究它。那次之后我把ST官方的STM32G0系列安全手册和相关参考文档翻了个遍又结合STM32CubeG0的驱动代码做了不少实测总算把这颗芯片的安全边界摸透了。说白了G0没有MPU也没有TrustZone它能给你的不是“可信执行环境”这种高级隔离而是一整套成本不高的硬件锁RDP读保护、PCROP专有代码保护、WRP写保护部分型号还带HDP隐藏保护区和AES硬件引擎再加上TRNG、唯一ID这些基础元件配合一套靠谱的启动校验和升级流程足以挡住绝大多数只想花两小时抄个固件的攻击者。这篇东西不是手册的翻译我会按做产品的顺序来先说这本手册到底让你防什么再拆每个安全特性的原理和坑然后给一份可落地的配置流程最后把我实际遇到的问题和排查方法整理成清单。适合谁看正用STM32G0做量产产品的工程师尤其是表计、家电、物联网终端、电动工具这类需要防抄板、防篡改、防恶意升级的项目。1. STM32G0安全手册到底在讲什么1.1 先分清两个“安全”信息安全和功能安全ST官方那份“STM32G0 series safety manual”严格来说偏功能安全方向讲的是硬件故障模式、诊断机制、错误处理是给做IEC 61508、ISO 26262这类功能安全认证的团队看的。但中文语境里多数工程师搜索“STM32G0安全”想解决的其实是信息安全问题固件会不会被读走、代码会不会被篡改、别人能不能通过调试器做坏事。这两个概念不冲突而且G0这一代把两者很多底层机制统一了。比如RDP读保护既是不敢随便碰的“保险丝”也是信息安全里最基础的一道锁。再比如独立看门狗IWDG、掉电复位BOR、时钟安全系统CSS这些手册里反复强调的机制表面看是功能安全要求实际上一旦有人做电源毛刺攻击、时钟故障注入它们也能起到实际的防御作用。所以我这篇以信息安全为主线同时会在关键地方点出功能安全相关机制。你不需要先搞懂SIL等级才能看但了解这些机制之后再去翻官方Safety Manual会轻松非常多。1.2 威胁模型你的板子可能面临哪些攻击我见过不少工程师一上来就问“怎么让固件读不走”但没想过自己的威胁模型是什么。做安全方案之前得先明白攻击者是谁、攻击成本有多高、你手里的资产值不值得花这个成本。按攻击难度从低到高G0产品最常见的攻击方式大概是这几种SWD调试口直读Flash。这是最廉价的攻击成本就是一根十几块钱的ST-Link懂点CubeProgrammer就能操作。大多数人固件被抄走都是这条路。通过Boot引脚进入系统Bootloader用串口等接口读Flash。如果选项字节没有把外部Boot关掉这招成本也很低。修改选项字节。比如尝试把RDP Level1降回Level0或改写PCROP、WRP配置。注意G0里RDP从Level1回退到Level0会触发整片擦除所以攻击者想用这招拿现成代码是拿不到的但可以用来清掉你的保护再重新灌恶意固件。电压毛刺和时钟故障注入。这个需要点硬件设备成本就上来了。攻击者会在芯片供电或时钟线上制造毛刺尝试让CPU跳过某个校验分支直接跳进应用代码。侧信道攻击和侵入式开盖分析。这类攻击针对的是带AES的型号通过功耗曲线推断密钥或者直接开盖用探针测内部总线。成本最高一般只有做安全研究的人才会这么搞。设计安全方案的原则其实很简单让攻击成本高于你的固件价值。G0这套安全特性组合起来能把前三种攻击几乎封死把第四种攻击的门槛抬高到一般人不会去碰的程度这就够了。1.3 安全手册里的设计原则不管看哪颗芯片的安全手册核心都绕不开四件事安全启动、安全更新、安全调试、安全存储。把这四件事想清楚方案就出来了。安全启动就是上电时先跑一段可信代码校验应用固件的完整性和来源通过才跳转。安全更新就是升级固件时保证新固件是官方签名的并且不能降级到有漏洞的旧版本。安全调试就是研发阶段可以方便调试量产之后把调试口彻底关上。安全存储就是密钥、证书、校准参数这类敏感数据不能裸放在Flash里让人直接读走。G0上没有TrustZone这种硬件隔离所以这四件事更多是靠“烧断保险丝”的思路来实现。接下来要聊的RDP、PCROP、WRP、HDP就是保险丝本身。2. 手册里最该吃透的硬件安全特性2.1 读保护RDP三级防护的逻辑与回退陷阱RDPRead Protection是STM32G0最基础、也最容易被误用的一级保护。它不是一个开关而是三个等级理解清楚每个等级的语义比背寄存器名重要得多。我先给你一张对照表RDP级别调试接口访问Flash运行中的CPU访问Flash回退后果适用场景Level 0完全开放完全正常无开发调试阶段Level 1禁止直接读取完全正常回退Level 0触发全片擦除量产最常用的锁定状态Level 2永久禁用完全正常不可回退一次性封闭场景Level1是最微妙的档位。芯片本身能正常跑但任何调试工具都读不到Flash和SRAM里的数据。注意SRAM也读不到所以别指望用调试器扒内存里的临时密钥。CPU自己访问Flash不受任何影响。Level1回退到Level0的规则是新手最容易踩坑的地方。一旦你执行回退芯片会强制整片擦除不只是用户Flash连选项字节以外的所有数据全部清空。这是硬件自动执行的不是软件行为目的是防止你“先降保护再读旧数据”。很多工程师第一次操作时把芯片回退之后发现程序没了、校准数据没了第一个反应是“坏了”其实这是正常现象。Level2就是真正的“烧断保险丝”。一旦设置调试接口永久失效系统Bootloader彻底不可用没有任何手段能回退。我见过有人在试产阶段开了一颗Level2后来想改个参数发现只能把芯片扔了换新的。所以除非你的产品完全不需要售后升级可以接受整机返厂换芯片否则不要轻易上Level2。2.2 PCROP专有代码保护回读不到但能跑PCROPProprietary Code Read-Out Protection是我个人觉得G0上最被低估的一个特性。它能把指定的Flash区域变成一块“只允许CPU取指执行不允许任何人读数据”的保护区。就算你把RDP降到Level0PCROP区域的代码内容依然无法通过调试接口或者任何读操作获取。这背后的原理是Cortex-M0核心访问Flash有两条路取指令走I-Bus读数据走D-Bus。PCROP区域对I-Bus是开放的CPU可以正常执行里面的指令对D-Bus是封锁的任何通过数据总线读这个区域的操作返回的都是无效数据。这个特性特别适合放核心算法、协议栈、私钥处理函数。我在一个表计项目里把整个计量校准算法和通信协议栈都丢进了一个PCROP页外面读Flash的人只能看到一段跳转指令再往里翻就是一堆空数据。但PCROP有个非常隐蔽的坑你不能在这个区域里放任何只读数据比如const数组、查表数据、字符串常量。因为编译器会把它们当成数据段运行时CPU会通过D-Bus去读结果就是读出来一堆意外的值程序要么跑飞要么行为变得完全不可控。我一开始把一张256字节的CRC表放进了PCROP区结果程序一跑到查表就跳飞排查了整整两天才发现问题。所以PCROP区的铁律是只放纯代码数据和常量一律放普通区域。如果核心算法需要查表表放普通Flash靠RDP保护或者把表加密存储运行时临时解出来。2.3 写保护与隐藏保护防篡改的另一个维度RDP和PCROP解决的是“能不能读”的问题WRPWrite Protection解决的是“能不能改”的问题。WRP可以锁定指定Flash页锁定之后既不能擦除也不能写入。这个不需要了解太多机制但使用场景很明确比如把Bootloader区和固件版本记录区用WRP保护起来防止攻击者篡改。很多人容易把WRP和PCROP搞混。记住一句话PCROP防读WRP防写。WRP不排斥读也就是说被WRP保护的区域如果RDP是Level0别人还是能通过调试器读走。所以WRP往往要跟RDP配合RDP管住读取WRP管住写入。HDPHidden Protection Area则是另一类玩法部分G0型号提供这个特性。它有点像“一次性隐藏区”芯片启动早期HDP区域对CPU是可见的你可以执行初始化代码、读密钥一旦走完配置流程触发隐藏条件这块区域对外就彻底不可见了连CPU自己都访问不到。这适合做一次性安全配置比如启动时加载根密钥然后立刻关闭对密钥区的访问。HDP的限制是配置完成之后没办法再改所以使用前要想清楚你的密钥轮换策略。如果产品有远程更新密钥的需求HDP不是好选择外部安全芯片或者多级派生方案更合适。2.4 调试口、启动配置与诊断机制容易被忽略的突破口除了Flash保护还有两个入口经常被忽视一个是调试接口一个是Boot启动路径。G0的调试口受RDP控制Level1下调试口基本连不上Level2下彻底禁用。但问题往往出在开发阶段很多人整个开发周期都开着Level0直到量产前才想起来锁一旦中间有人拿到板子固件早就被摸透了。我现在的习惯是评审阶段就开始给板子设置为Level1调试用“连接复位后重新烧录”模式项目定型后只在最后一版做解锁操作。系统Bootloader入口也要管。G0从系统Bootloader进入后芯片会执行ROM里的固化程序它支持通过USART或I2C等接口读写Flash。如果选项字节里的启动配置没有关掉攻击者把BOOT0引脚拉高再配合复位就能通过串口把固件拉出来。量产板子务必把外部Boot引导关闭或者把引脚固定在不会触发Boot的状态。功能安全方向上手册里还花了不少篇幅讲诊断机制独立看门狗IWDG、掉电检测BOR、时钟安全系统CSS、复位标志寄存器。这些机制在防篡改场景里一样有用。比如BOR设置合理的掉电阈值可以防止攻击者把电压拉到正常工作范围之外让CPU在不确定状态下运行CSS则是外部晶振失效时自动切换到内部时钟并产生复位防止攻击者通过断晶振来干扰程序流程。这些不是锦上添花而是完整安全方案的一部分。3. 实操从零配置一套可落地的安全方案3.1 先定目标你到底要防谁我一般会先带着客户或同事把安全目标写清楚再决定开哪些特性。不同产品防的东西不一样组合方案也不一样。安全目标关键威胁推荐组合核心注意事项防抄板固件被读走、算法被还原RDP Level1 PCROP核心算法 WRP保护Boot区PCROP区不要放数据常量防篡改固件被改写人货场被替换WRP 启动时完整性校验校验算法要控制启动时间防恶意升级只运行自家签名固件签名校验 版本号单调递增私钥永远不能放在设备上防调试泄露调试口被利用、内存被读取RDP Level1/Level2 关闭外部Boot量产前做一次完整的锁片验证我自己最常用的量产组合是RDP Level1 PCROP保护核心算法 WRP保护Bootloader区和版本记录区 关闭外部Boot入口。这套组合对绝大多数物联网终端和工业控制产品都够用而且不增加硬件成本。3.2 选项字节配置图形化工具和代码方式选项字节Option Bytes是G0上所有保护特性的总开关。有两种配置方式量产初期我推荐用STM32CubeProgrammer图形化操作简单直观产线批量烧录或者需要程序自升级时再考虑用代码方式。图形化操作流程大致是这样先正常编译、烧录应用固件确保功能没问题。打开STM32CubeProgrammer连接目标板。进入左侧的Option Bytes页面。在Read Protection栏选择Level 1。在PCROP栏配置起始页和保护的页数注意必须是页对齐。我通常把核心算法代码单独安排在一个连续区域正好按页对齐。在Write Protection栏选择需要保护的页。检查一遍然后点击Apply。芯片会触发一次系统复位选项字节生效。有人习惯“先锁定再烧录”觉得这样更安全但实际批量生产时容易出问题。我的建议是先烧录完所有出厂固件和初始化数据最后再做锁片操作这样任何一步失败都可以回来重来不会直接废板。代码方式可以这样操作用STM32CubeG0的HAL接口/* 示意代码具体寄存器定义和接口以STM32CubeG0固件包为准 */ HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); FLASH_OBProgramInitTypeDef ob {0}; ob.OptionType OPTIONBYTE_USER | OPTIONBYTE_PCROP | OPTIONBYTE_WRP; ob.RDPLevel OB_RDP_LEVEL_1; /* 配置PCROP起始页和结束页 */ ob.PCROPStartAddr (uint32_t)0x08006000; ob.PCROPEndAddr (uint32_t)0x08007FFF; /* 配置WRP保护区域 */ ob.WRPStartAddr (uint32_t)0x08000000; ob.WRPEndAddr (uint32_t)0x08003FFF; HAL_FLASH_OB_Program(ob); HAL_FLASH_OB_Lock(); HAL_FLASH_Lock();注意代码写进去之后需要触发一次系统复位选项字节才会重新加载。HAL_FLASH_OB_Launch这个接口就是干这个的跑完它会直接复位所以调用前确认没有任何关键数据还没保存。3.3 安全启动和固件升级校验、签名、防回滚G0没有硬件安全启动引擎所以安全启动逻辑是靠Bootloader自己实现的。这也是我认为整个G0安全方案里最难、也最有价值的部分。一个可以落地的启动流程长这样CPU复位后先执行Bootloader区的启动代码。这个区域我建议用WRP保护防止被篡改。Bootloader配置好系统时钟、关掉不必要的外设尽量减少攻击面。从固定Flash地址读取App固件的长度、版本号、签名值或哈希值。计算整片App固件的SHA256哈希。G0主频64MHz没有硬件SHA引擎纯软件计算一块64KB的固件大约几十到上百毫秒这个开销在产品里通常可以接受。用内置公钥验证固件签名或者用AES-CMAC验证消息认证码。带AES引擎的G0型号做AES-CMAC会快很多。检查固件版本号必须大于或等于上次记录的版本号防止攻击者把固件降级回有漏洞的旧版本。全部通过后跳转到App入口。如果校验失败不要直接挂死进入升级模式等待上位机下发新固件。防回滚是很多人漏掉的一环。只校验签名、不校验版本攻击者可以把一台已经通过远程升级修复漏洞的设备强行刷回旧的带漏洞版本然后利用漏洞做事。所以版本号必须结合签名一起封装进固件包里并在Flash里单独分出一块区域记录当前版本号这块区域最好也用WRP保护。我见过有人把版本号记录在App自身占用的Flash空间里结果App升级时一擦除版本号也没了整个防回滚机制形同虚设。固件升级的传输链路也要防。如果固件通过UART、SPI或者无线信道传输攻击者可以做中间人替换。最稳的做法是传输链路只负责搬运数据固件包本身就带签名接收端只信任签名。包体内必须包含设备ID或产品ID防止攻击者把另一个产品的合法固件搬过来灌进去。3.4 密钥、TRNG和唯一ID别把密钥写死在代码里很多人做安全方案最后都卡在密钥管理上。G0没有独立的安全元件所以密钥存储本身就是个挑战。但也不是没办法。第一步别把AES密钥、签名私钥明文写在普通Flash的const数组里。你以为代码编译成bin之后别人看不到实际上用文本编辑器打开固件文件搜一搜16字节的连续可打印字符串经常能搜到东西。更不要提生产固件经常外发给代工厂泄密渠道多得很。我常用的做法是分散密钥。G0内部有96位唯一ID每颗芯片都不一样。我可以用唯一ID作为KDF输入配合固定的根密钥派生每颗设备自己的业务密钥。这样即使某台设备被攻破密钥也只在那一台上有效不影响整个产品线。原厂量产时我甚至会把根密钥从固件中剥离改成通过烧录工具单独灌入这样代工厂拿到的固件里根本没有密钥。第二步用TRNG生成随机数。G0部分型号带硬件真随机数发生器用起来很方便。安全启动协议里如果需要挑战-应答或者生成一次性随机数用于防重放TRNG是必须的。软件伪随机数在这种场景下不可靠攻击者完全可以通过预测种子绕过校验。第三步密钥使用时间尽量短。把密钥加载到SRAM中用完立刻擦掉不让它常驻。配合RDP Level1调试器读不到SRAM泄露面就小很多。如果有HDP的型号密钥可以放在HDP隐藏区用完就永久关闭访问权限这是G0上能做到的最接近“安全元件”的方案。4. 常见问题与排查实录4.1 那些年我遇到的翻车现场我在这条路上踩过不少坑挑几个典型的说出来你能少走弯路。第一个坑设置了RDP Level1之后ST-Link死活连不上。当时我以为是芯片坏了差点下单买新片。后来确认Level1下调试器需要“Connect Under Reset”模式才能重新建立连接。CubeProgrammer左上角设置里把Mode改成Under Reset再连接就能连上然后执行Full chip erase芯片就能回到Level0。这个模式很多人没注意一锁就慌其实根本没那么可怕。第二个坑把const数组放进了PCROP区程序运行起来各种诡异。前面提到过查表数据通过D-Bus读取PCROP区直接把这条路堵死了所以CPU读出来的全是垃圾值。排查方法也很简单把PCROP区临时关闭程序立刻正常那基本就是这个问题。解决办法是把数据区域全部移出PCROP区。第三个坑从Level1回退Level0想把上次烧录的版本读出来做对比结果发现Flash已经是空的了。这个真的是手册里写了但我没当回事的规则。Level1降级Level0必然触发全片擦除不是“可选项”而是硬件强制的。所以任何需要在售后阶段保留的校准数据、出厂序列号都要在第一次烧录时就同步备份到外部EEPROM或Flash的独立区域别指望解锁后还能读回来。第四个坑量产软件里忘了处理Boot引脚结果整批板子在客户现场被人拉高BOOT0进入了系统Bootloader通过串口刷了一版恶意固件。这其实不是技术难事就是配置选项字节时漏掉了外部Boot关闭的选项。量产前的检查清单里必须安排一项确认外部Boot引导已关闭或引脚固定为高电平。4.2 一份可以直接抄的检查清单我把自己每个G0项目投产前都会过一遍的清单放在这里照着检查基本不会漏检查项目标状态配置方式常见坑RDP级别Level1或Level2选项字节Level2不可逆别在试产阶段开PCROP区域只包含纯代码选项字节数据/常量会触发诡异行为WRP保护Boot区、版本记录区选项字节不防读必须配RDP使用外部Boot入口量产关闭选项字节/引脚固定工厂测试时可能需要临时打开调试口量产锁定RDP控制开发阶段别锁太早固件签名每次启动校验Bootloader代码私钥不能出现在固件文件里版本防回滚只升不降独立Flash记录签名版本记录区要和App区隔离出厂校准数据独立备份外部EEPROM/独立扇区解锁RDP会全擦备份必须在锁片前完成4.3 生产环境和供应链安全安全不只发生在芯片上生产环节同样重要。如果固件原文件、烧录脚本、私钥直接放在产线电脑里那整个安全方案就白做了。我见过不少公司把打包好的固件直接扔给代工厂代工厂再用普通烧录器复制到每一片芯片上。这种情况下固件文件本身已经泄露了芯片锁得再死也没有意义。所以如果走代工要么用加密烧录工具要么把固件分两部分一部分是固定的、不敏感的启动框架可以交给代工厂另一部分是带密钥的敏感信息必须在自己的产线里完成烧录和锁片。另外每片板的唯一ID和生产记录要提前想好怎么管。G0的96位唯一ID可以在出厂前读出来写入MES系统后续做返修、追溯、激活码校验都依赖这个ID。如果提前不做采集等板子贴片封装之后再想获取唯一ID就只能通过产品内部固件往外报链路就长了。5. 个人经验与扩展技巧5.1 安全特性不是堆得越多越好我见过一位同事把RDP Level2、PCROP、WRP、HDP全部打开以为这样最安全。结果产品上市后遇到一次固件缺陷需要升级由于整个芯片被彻底锁死只能整机召回返厂换芯片。这个教训很贵。安全方案讲究的是匹配风险等级。大部分量产产品做到RDP Level1 PCROP 签名启动就已经足够了Level2是给那种“一次性激活、终身不可维护”的场景用的。我在实际操作中形成了一个原则任何配置在量产前都要验证两遍一遍验证功能正常一遍验证锁定后还能不能走预定的升级路径。如果升级路径走不通这个方案就不该上线。5.2 后续还可以继续扩展的方向如果你觉得G0原生安全方案还不能满足要求可以考虑几条扩展路线。第一外部安全芯片。G0通过I2C或SPI挂一颗SE050这类安全元件密钥、证书、验签全部丢给外部芯片处理G0只负责业务逻辑这是目前物联网设备里被验证过的比较靠谱的做法。第二云端联动。利用G0的TRNG生成随机数做设备侧挑战应答配合云端激活校验即使固件被抄走抄板的人也很难伪造出能通过云端认证的设备。第三离线产品可以考虑在固件里加入基于唯一ID的设备指纹启动时校验硬件是否匹配防止整片Flash搬到另一颗芯片上跑。最后分享一个成本很低但效果不错的小技巧用G0的96位唯一ID配合你自己定义的一个简单派生算法在启动时生成一组动态校验码通过串口或CAN总线上报给上位机。上位机根据ID库里的算法反向验证几十毫秒就能判断这台设备是不是原厂合法固件。这个方案不需要增加任何硬件成本对防抄板和防伪激活非常有效我后续好几个项目都在用。安全这东西永远没有绝对但每加一道锁攻击者的时间成本和设备成本都会高一分你的产品被盯上的概率就会低一大截。