
我们调试一款联网设备时遇到过一件挺糟心的事攻击者把固件从 Flash 里硬读出之后翻一翻二进制文件直接就把烧录在明文里的 AES 密钥提走了。更离谱的是对方连设备上的固件都没替换只是改了几个配置字节就绕过了整套业务逻辑校验。那会儿我最大的感受是MCU 上的保密不是加个密读保护位就完事儿的启动链、密钥存储、数据分区、回滚防护缺一环都白搭。这也是为什么后来看到 PSA Certified 体系里反复强调 Secure Flash Storage安全闪存存储时我会特别关注——它不是给你发一张证书就结束的营销概念而是从信任根、安全启动到安全存储 API 一整套可落地的架构方案。这篇内容主要围绕 PSA Certified MCU 如何构建设备端闪存安全存储来写先讲清楚 MCU 数据保护的根本矛盾再拆解 PSA 安全模型里和存储相关的核心机制包括信任根、安全启动、密钥管理与分区隔离然后分析项目里用到的 TF-M 和 PSA Secure Storage API 的具体工作方式最后分享我在实际设备上跑安全存储时的踩坑与验证经验。无论你是在做门锁、表计、工业网关还是智能家居主控只要设备里需要存放密钥、证书、校准数据或业务敏感参数这篇都值得花十分钟看完。1. 为什么 MCU 上存个密钥这么难从明文 Flash 到信任锚点1.1 单片机 Flash 存储的传统思路与隐患在 PC 上我们可以依赖操作系统权限、TPM 芯片、可信执行环境TEE来做数据隔离但单片机不是这么玩的。MCU 的资源极度受限Flash 和 RAM 按 KB 到 MB 规模计算很多场景连 MMU 都没有更谈不上进程级内存隔离。所以大多数嵌入式项目的做法就是直接把密钥、服务器地址、校准参数写死在代码里或者放到独立的 Flash 分区中靠编译期混淆和读保护位来防君子不防小人。这里的关键问题在于代码中所有明文存储的敏感数据都会被固件分析工具轻而易举地定位。比如你用strings命令扫一遍固件镜像所有可见字符串都暴露了就算你把密钥拆成 byte 数组只要知道存储位置还是能被经验丰富的逆向工程师提取出来。更麻烦的是很多 MCU 的读保护RDP机制是可以被攻击者利用电压毛刺、激光探针等手段绕过的单一保护措施几乎不存在绝对安全。1.2 数据安全的核心矛盾既要保存又不能被读取看到这里你可能会说那我把 Flash 里的数据加密存储不就行了对但随之而来的问题是——解密用的密钥又存在哪里如果密钥仍然和密文放在同一颗 Flash 上、同一把保护伞下那么攻击者只要拿到 Flash 镜像就用同一把钥匙打开了同一扇门。安全存储的难点从来不在于加密算法本身而在于密钥的存储位置和使用权限链。这也正是 PSA Certified 模型里先谈信任根Root of TrustRoT而非先谈加密算法的原因。安全模型的第一步是让设备拥有一块绝对可信的硬件锚点所有的敏感数据保护都从这块锚点出发密钥要么派生自这块锚点要么被这块锚点加密后存储。PSA Certified 的认证要求中对信任根、安全启动、安全存储、安全生命周期管理都有明确的能力等级它的落地思路不是给你一套银弹算法而是把可信边界明确地划出来。1.3 我在项目里对PSA Certified MCU Safe Storage的实际需求我做过的一款工业数据采集器运行商密算法与业务网关做双向认证。设备端需要保存三样敏感数据设备证书私钥、网关根证书、固件升级用的验签公钥。最初我们把这些数据放进内部 Flash 的末尾扇区启用 RDP Level 1然后所有固件访问全靠自定义的存储驱动。联调测试是全部通过的但在做安全评估时模拟攻击者直接从 Flash 引脚飞线读取了整片数据——我们才发现自定义存储方案的威胁模型里根本没有考虑物理接触攻击者的情况。后来切换到带 PSA Level 1 Level 2 认证的 MCU 平台使用 TF-M 里的 Secure Storage 服务密钥全部通过硬件 crypto cell 加密后落盘Flash 里的密文即使被完整读出也无法直接使用。整个改造不是换一颗芯片那么轻松它涉及启动链、分区表、IPC 调用、密钥生命周期的整体重构。但也正是这次迁移让我把 PSA 认证体系的细节从听过变成了懂。2. 理解 PSA Certified 的存储前提信任根与安全启动必须先行2.1 RoTRoot of Trust钥匙中的钥匙信任根这个概念通俗点讲就是设备上最初的那点可信代码和可信密钥。PSA Certified 的架构里RoT 可以拆成几个角色代码 RoT 负责启动链的可信验证平台 RoT 负责密钥管理、存储与密码学服务。MCU 中通常对应内部的 Boot ROM 和一组唯一的根密钥如 Device Unique KeyDUK。以我在项目里用的 Arm Cortex-M33 平台为例它的 TrustZone 机制把 CPU 分成了安全世界和非安全世界。安全世界运行 TF-M 内核非安全世界运行应用程序。所有敏感数据存储动作从硬件上就被隔离在安全世界内部非安全世界的代码只能通过专用 API 请求服务无法直接触碰存储介质和密钥。这就是信任根硬件隔离的组合拳信任根负责启动链TrustZone 负责运行期隔离两者合起来才构成安全存储的底座。2.2 安全启动流程如何保证谁在看数据为什么讲安全存储必然会牵扯到安全启动因为存数据这件事本身没有意义关键是谁在什么阶段读取和解密这些数据。如果启动链是假的、固件被篡改了那么再强的加密存储也只会把密钥拱手交给恶意代码。PSA Certified 要求设备从复位向量开始就建立信任链Boot ROM 里固化一段不可修改的代码它首先验证 Bootloader 的签名Bootloader 再验证固件镜像的签名逐级建立可信链。只有当固件验签通过之后非安全世界的业务代码才被允许执行。这样安全存储服务的使用者从一开始就是经过认证的密钥不会泄露给非授权代码。实测下来的感受是很多项目初期不重视 Secure Boot认为我存密钥的分区加了读保护别人读不出来。但读保护只能阻止静态读取当攻击者直接把你的固件替换成一段恶意代码、让它原地解密数据再通过串口吐出来时你的存储方案就形同虚设。Secure Flash Storage 的前提是 Secure Boot这不是营销约束而是安全逻辑的必然。3. 安全闪存存储的核心机制密钥管理、加密落盘与反回滚3.1 密钥从哪来Device Unique Key 与 Key WrappingPSA 的 Secure Storage 不会直接把应用层给的密钥明文写到 Flash而会先用设备唯一密钥Device Unique Key或从 OTP/安全元件中派生的 KEK对存储密钥做一层包装。这样外存的密文被攻击者读取后在没有 DUK 的平台上就无法解密更别提通过直接将 Flash 挪到另一颗 MCU 上实现数据克隆。从流程上看TF-M 的 Secure Storage 实现了标准的 key wrapping 模式你将敏感数据私钥、证书、会话密钥通过 PSA API 提交服务内部产生一个随机密钥对数据加密然后这个随机密钥再被 DUK 包装后存储。数据区与密钥区区分存放攻击者即使拿到整个镜像也缺少解包的信任根。我在实际代码中见过不少开发者试图绕过这个流程自己定义一套加密文件系统结果往往在随机源、密钥槽管理、备份方案上漏洞百出。既然 PSA API 已经给出了标准模式最稳妥的做法就是按它的机制来。3.2 数据加密细节GCM/CCM 之外还要防篡改很多工程师以为用 AES 把数据加密存进 Flash 就是安全存储但真正做过安全方案的人会强调认证加密AEAD而不是单纯的机密性加密。PSA Certified 安全存储默认建议使用带认证的加密算法如 AES-GCM、AES-CCM这样密文在存储期间被任何原因改动物理攻击、比特翻转、恶意篡改读取时认证标签都会校验失败。我踩过一个跟这相关的坑某次为了提升性能我把存储从 GCM 换成了 CBC 模式结果测试团队模拟 Flash 区域比特翻转后数据解密出来是正常的乱码程序并没有立刻发现数据被篡改一直到业务逻辑计算出错误结果才暴露问题。从那以后我坚持所有敏感存储必须走 AEAD认证失败直接报错并把该数据分区标记为失效绝不宽容地返回部分解密数据。对密钥、证书这类关键资产来说明确报错比静默返回坏数据安全得多。3.3 分区隔离与反回滚保护防止降级攻击安全存储的另一项重要机制是反回滚Anti-Rollback。攻击者如果无法直接篡改你的数据内容可能采取更狡猾的手段把整个 Flash 分区恢复到某个旧版本使设备回退到曾经合法但已过时的状态进而利用旧版本固件的已知漏洞。PSA Certified 的信任根和 Secure Storage 规范里明确要求对固件版本号、安全关键数据版本号做单调递增保护。一旦写入新版本旧版本的数据就不可被重新激活。在具体实现上TF-M 使用了一种基于单调计数器和加密认证的版本管理机制。我习惯把版本计数存储在 OTP 或带独立电源的 RTC 备份寄存器中因为纯 Flash 计数器本身是可以被擦除重置的。如果你的平台不支持 OTP 计数器建议至少将其存储在受写保护的安全 Flash 区域并由 Secure Storage 服务同时校验数据和版本签名。4. 用 TF-M 搭建 Secure Flash Storage 的实际落地流程4.1 硬件与软件平台选型如果从零开始做 PSA 安全存储我建议直接选择支持 Arm TrustZone 的 Cortex-M23/M33/M55/M85 系列 MCU并启用 TF-MTrusted Firmware-M。TF-M 是 Arm 官方开源的安全固件框架实现了 PSA Firmware Framework 和 PSA Secure Storage API像 STM32L5、STM32U5、NXP LPC55xx、瑞萨 RA 系列等主流 PSA Certified MCU 平台都有现成的 TF-M 移植。选型时可以重点关注几个指标安全世界可用 RAM/Flash 大小、硬件 crypto cell 是否支持 AES-GCM/CCM、是否有 OTP 或 eFuse、TrustZone 是否支持多分区隔离。我用的开发板是高性价比的 Cortex-M33 平台内置 512KB Flash、256KB SRAM安全分区划分给了 64KB Flash 和 48KB SRAM跑 TF-M Secure Storage 服务外加错误处理逻辑空间仍比较宽裕。4.2 TF-M 中启用 Secure Storage 的配置步骤TF-M 的构建配置通过 CMake 菜单完成关键开关在CMakeConfig/default配置文件中。我先列一组在项目里验证过的配置要点打开TFM_PSA_API启用 PSA API 支持让非安全应用可以通过 IPC 调用安全服务。打开TFM_PARTITION_PROTECTED_STORAGE和TFM_PARTITION_INTERNAL_TRUSTED_STORAGE这两个分区分别对应外部 Flash 加密存储和内部安全存储。将TFM_PSA_API_TEST关闭避免测试代码额外占用安全分区资源。设置ITS_BUF_SIZE和PS_BUF_SIZE时注意不要超过安全分区 RAM 容量。一般 PSProtected Storage缓冲区 512 字节足够大多数场景。构建完成之后非安全世界通过psa_ps_set()、psa_ps_get()等接口访问数据。以写一个设备证书私钥为例psa_status_t status; psa_storage_uid_t uid 0x10001; // 自定义UID用于标识数据项 struct psa_storage_info_t info {0}; // 创建存储写入策略仅允许安全世界访问 psa_storage_create_t create_flags PSA_STORAGE_FLAG_WRITE_ONCE; // 写入私钥数据 status psa_ps_set(uid, strlen((char*)key_data), key_data, create_flags); if (status ! PSA_SUCCESS) { // 处理错误 } // 读取私钥数据 uint32_t data_length 0; status psa_ps_get_info(uid, info); if (status PSA_SUCCESS) { data_length info.size; } status psa_ps_get(uid, 0, data_length, read_buffer, data_length);这里有个容易被忽略的点PSA_STORAGE_FLAG_WRITE_ONCE是真正防止覆盖的标志但如果只是普通写入后续调用psa_ps_set()是可以覆写同 UID 数据的。如果业务上要求首次写入后永不可变务必加上这个 flag。4.3 与业务代码联调IPC 调用与权限配置非安全代码调用安全存储服务本质上是一次 IPC 请求。TF-M 通过 SPMSecure Partition Manager分发请求需要在非安全端实现 IPC client。大多数 PSA Certified MCU 的 SDK 都已经封装好了psa_ps_*接口底层通信方式对应用层透明。但如果你的 SDK 比较老可能还需要手动配置非安全中断和 mailbox 地址这部分最容易在休眠唤醒后出错。我的经验是联调第一件事不是测试存储功能而是验证低功耗模式唤醒后 IPC 通信是否依然正常很多平台深睡后安全分区的时钟恢复不及时会导致 PSA API 返回PSA_ERROR_COMMUNICATION_FAILURE。另外TF-M 的 Secure Storage 分区在初始化时会自动检查数据完整性如果之前的数据在意外掉电时被写坏它会返回特定错误码。为了拿到更直观的日志我建议开发阶段打开 TF-M 的TFM_SPM_LOG_LEVEL和TFM_DEBUG_LOG_LEVEL便于看到信任根初始化、密钥派生、分区加载等流程的详细打印。量产前再关闭日志避免敏感信息泄漏。5. 不换主控也能补课给存量产品增加安全存储的替代方案5.1 软件方案利用 TrustZone 和已有的 Flash 控制器如果你的现有 MCU 已经支持 TrustZone 但没有官方 TF-M 移植理论上仍可以自己实现一套精简版的安全存储。思路是在安全世界分配一块专有 Flash 分区用安全启动保证只有安全世界代码能被加载所有敏感数据先经硬件 crypto cell或软件实现的 AEAD加密后落盘密钥保存在 OTP/eFuse 中。但这个方案的开发量和验证周期都不小尤其安全启动链的签名机制、密钥备份方案、异常容错都不太可能在短期内做到工业级强度。我帮一个做光伏逆变器的团队评估过这种改造结论是可以做但工作量至少相当于一个专职安全工程师做六到八个月。而且如果后续想让产品拿到 PSA Level 2 认证自研方案必须通过第三方实验室的安全评估隐性成本更高。所以我的建议是新项目直接选 PSA 认证 MCU存量项目优先考虑外部安全芯片方案。5.2 硬件方案外挂 Secure Element 或标准安全芯片不换主控时最稳妥的路径是增加一颗安全芯片Secure Element。安全芯片自带 CC EAL 5 或更高等级的安全认证提供密钥生成、签名验签、安全存储等全套能力。主控侧只需要通过 SPI 或 I2C 与安全芯片通信敏感数据交由 SE 内部处理并加密存储密钥永远不会以明文形式出现在主控 Flash 里。这种方案的优点是灵活存量主控不需要大规模改板通信接口和驱动相对简单安全等级由独立芯片保证你不用自己维护复杂的信任根逻辑。缺点是成本增加和时序延迟尤其 RSA/ECC 签名操作可能达到几十到几百毫秒业务上需要接受这个延迟。适合路由器、网关这类对时延不敏感、但对安全等级有硬性要求的设备。5.3 方案选择对比这里我把两类方案放在同一张表格里方便大家在选型时做决策维度自带 PSA 安全存储的 MCU主控 外部安全芯片开发成本中等依赖 TF-M/SDK较低安全逻辑集中在 SE安全等级与 MCU 认证等级相关可到 Level 2取决于 SE 芯片等级通常较高密钥存储位置芯片内部 OTP/信任根派生SE 内部独立安全存储性能开销IPC 调用 硬件加解密一般几毫秒通信协议开销 签名计算延迟升级迭代固件升级需同步更新安全固件只需更新 SE 固件或密钥全生命周期管理需要自己打理固件签名与密钥轮换SE 厂商通常提供完善工具链如果说 PSA 认证 MCU 适合高集成度、低成本、大批量产品那么外挂安全芯片更适合旧平台快速升级、以及对安全等级要求更高的基础设施类设备。没有哪个方案一定最好关键看你的威胁模型、成本预算和团队维护能力。6. 实测踩坑记录从密钥恢复失败到 Flash 磨损带来的教训6.1 密钥恢复灾难Secure Storage 数据抄不了板测试 Secure Storage 功能时我们曾尝试把一颗已经烧录好数据的 MCU 上的 Flash 完整搬到另一颗同型号 MCU 上期望模拟攻击者克隆设备的行为。结果和新设备的启动行为完全一致——安全存储服务报错数据无法读取。原因是每颗 MCU 的 Device Unique Key 不同即使 Flash 内容完全一致KEK 解密也过不去。这说明密钥派生与芯片绑定的机制确实有效设备无法被简单克隆。但这也带来了一个运维挑战如果产品需要售后维修主控一换之前存储的密钥和证书就全部失效。所以我们在项目里专门设计了安全备份/恢复流程通过专用维护工具和 SE 内部的管理密钥预先导出一份经过管理员公钥加密的备份数据。普通故障换板后必须由售后人员通过管理员授权把备份解包并重新写入新设备。这个流程增加了售后成本但安全产品想要大规模落地这是绕不开的一步。6.2 掉电损坏、磨损均衡与数据校验Secure Storage 分区同样位于 Flash 上Flash 的擦写次数有限如果高速写入日志或者频繁更新密钥轮换分区很快就可能耗尽寿命。TF-M 的 Protected Storage 和 Internal Trusted Storage 实现里虽然也有磨损均衡思想但不同移植版的具体策略差别很大。我建议把高频写入的敏感数据尽量放在 ITInternal Trusted Storage分区低频但大容量的数据放 PSProtected Storage分区并在应用层做写入频率控制。掉电损坏是另一个容易被忽视的问题。哪怕有 AEAD 认证如果在写入过程中掉电Flash 中可能是半个扇区的脏数据。TF-M 从设计上会通过事务记录和掉电恢复机制来保证数据一致性但只对特定分区大小和写入模式生效。我在测试中遇到过极端情况写大块数据超过内部缓冲时掉电重启后部分数据是旧版本、部分是新版本认证校验虽然发现了问题但数据已经处于不一致状态。解决思路是应用层尽量控制单次写入数据量不超过内部缓冲大小并使用数据版本号完整性校验的应用层双记录机制。6.3 性能开销与掉电策略的权衡最后聊一下性能。安全存储的本质是加密读写必然比普通 Flash 读写慢一拍。我实测下来在 Cortex-M33 80MHz 平台上写入 256 字节数据大约耗时 2ms 到 5ms读取差不多 1ms 到 3ms具体取决于硬件加解密引擎是否启用以及是否启用了防回滚校验。对于需要高频读写敏感状态的业务建议把它们放到普通内存变量中暂存只在关键节点同步到 Secure Storage。另外注意在掉电前预留足够的时间来 flush 关键数据。我们的设备外接了大电容检测到掉电后会进入掉电保护中断此时预留 50ms 时间把关键状态写入 Secure Storage。实测 50ms 能完成 256 字节数据的加密落盘但不建议做更大数据量。如果你需要保存一整个证书链建议采用分块写入或者提前在普通区内做预校验避免掉电中断期间长时间占用 Flash 操作。7. 给刚上手 PSA 安全存储的团队一些实在建议如果团队正在计划把 PSA Certified MCU 的安全存储能力引入产品我建议从三个方面着手。第一不要把买一颗认证芯片当成安全的终点。PSA Certified 认证是对硬件和默认固件的安全能力背书但最终产品是否安全取决于你如何使用这些能力——启动链是否强制校验、密钥是否妥善导入、安全分区是否按最小权限原则划分。第二尽早把安全生命周期管理纳入产品规划包括设备唯一密钥的签发、批量生产时如何灌装密钥、售后换板如何恢复凭据、固件升级时的签名与版本管理这些流程一旦产品量产后再补代价会高得多。第三建议先跑通 TF-M 的参考示例再做裁剪不要一上来就自定义协议。另外也可以参考这些实践中反复出现的检查项Flash 分区的安全属性是否配置正确安全与非安全中断是否有冲突低功耗模式下安全外设时钟是否正常安全存储数据的备份与恢复流程是否经过演练。安全产品不是做一个演示 Demo 就算完事真正的挑战在于长期运行、掉电异常、批量生产、攻击者不断试探这些真实场景。我自己在这类项目上最大的收获是明白了一个朴素的道理安全的本质是信任边界的清晰定义而 Secure Flash Storage 只是把边界从靠代码混淆和不公开方案提升到了靠硬件信任根和标准化安全服务的水平。把这个边界定义清楚了你才真正拥有了一台自己都读不了自己密钥的设备——而这也正是它不会被攻击者轻易逆向的原因。