1. 安全启动到底在防什么?先搞清楚威胁模型
很多刚接触汽车电子MCU的兄弟一听到“安全启动”四个字,第一反应就是“上个加密芯片就完事了”。我刚开始做ECU开发那会儿也这么想,后来被现实狠狠教育了一顿。安全启动不是加个密就完事,它本质上是一套信任链传递机制,核心目标是确保MCU从上电那一刻起,执行的每一行代码都是你签过名的、没有被中间人动过手脚的。
先说说威胁模型。你想想,一辆车上的ECU,比如刹车控制器、转向助力模块,如果攻击者能通过OBD口或者某个CAN节点把恶意固件刷进去,后果是什么?刹车可能在你踩下去的时候延迟200ms响应,转向可能在你高速变道的时候突然反向助力。这不是危言耸听,这是安全启动要防的第一类威胁——固件篡改。
第二类威胁是固件回滚。假设你发现了一个安全漏洞,通过OTA推了一个修复版本。攻击者拿到旧版本固件,利用旧版本里的漏洞重新刷进去,你的修复就白做了。所以安全启动不仅要验签名,还要验版本号,防止降级攻击。
第三类威胁是中间人替换。攻击者在OTA传输过程中把固件包替换成自己构造的恶意包,或者在生产线上通过调试接口直接写入未签名的固件。这类攻击在供应链环节尤其常见,所以安全启动必须从BootROM开始就建立信任根。
那HSM在里面扮演什么角色?HSM全称Hardware Security Module,硬件安全模块。你可以把它理解成MCU内部的一个“保险柜”,里面有自己的CPU、自己的RAM、自己的Flash,还有真随机数发生器和加密加速引擎。主核想访问HSM里的密钥?门都没有,密钥永远不出HSM的边界。主核只能请求HSM“帮我算个CMAC”或者“帮我验个签名”,HSM算完把结果返回,密钥本身全程不暴露。
CMAC又是什么?CMAC全称Cipher-based Message Authentication Code,基于分组密码的消息认证码。你可以把它理解成给数据打一个“防伪指纹”。发送方用密钥和消息算出一个固定长度的标签,接收方用同样的密钥和消息再算一遍,两个标签一致就说明消息没被改过。CMAC相比HMAC的优势在于,它直接复用AES等分组密码引擎,不需要额外的哈希硬件,在资源受限的MCU上特别合适。
所以整个安全启动的逻辑链条是这样的:BootROM里固化了一段不可修改的代码,它包含信任根公钥的哈希值。上电后BootROM先验Bootloader的CMAC,验过了才跳转到Bootloader。Bootloader再验Application的CMAC,验过了才跳转到Application。每一级都验下一级的完整性和真实性,形成一条从硬件信任根到应用层的完整信任链。HSM负责在每一级验签时提供密钥和加速运算,CMAC负责生成和校验消息认证码。
这套机制解决的核心问题是:确保只有经过授权的固件才能在MCU上运行。适合谁来参考?做汽车电子ECU开发的嵌入式工程师、负责OTA升级方案的系统架构师、以及需要过ISO 21434或EVITA认证的安全工程师。如果你正在用Infineon AURIX、NXP S32K、瑞萨RH850这类带HSM的汽车MCU,这篇文章就是给你写的。
2. 方案选型:为什么是HSM+CMAC而不是软件加密或外部SE
2.1 三种安全启动方案的横向对比
在确定用HSM+CMAC之前,我评估过三种主流方案,这里把对比结果摆出来,方便你根据自己项目的情况做选择。
| 对比维度 | 纯软件加密方案 | 外部SE芯片方案 | HSM+CMAC方案 |
|---|---|---|---|
| 信任根位置 | Flash中的Bootloader | 外部SE芯片 | MCU内部BootROM+HSM |
| 密钥存储安全性 | 低,可被读出 | 高,SE内部 | 高,HSM内部 |
| 验签速度 | 慢,CPU软算 | 中等,受限于通信接口 | 快,硬件加速 |
| BOM成本 | 最低 | 增加SE芯片成本 | 零额外BOM成本 |
| 防物理攻击 | 无 | 有,SE有防拆设计 | 有,HSM有主动屏蔽层 |
| 适用场景 | 非安全关键ECU | 高安全等级ECU | 汽车电子主流ECU |
纯软件方案的问题在于密钥必须存在主Flash里,攻击者只要把Flash读出来就能拿到密钥,整个安全启动形同虚设。我见过一个项目为了省成本用了纯软件方案,结果渗透测试的时候被白帽子十分钟就破了,最后不得不返工重新设计。
外部SE芯片方案安全性确实高,但问题是增加了BOM成本和PCB面积,而且SE和MCU之间的通信接口本身也可能成为攻击点。对于大多数汽车ECU来说,MCU自带的HSM已经足够满足需求。
HSM+CMAC方案的核心优势在于信任根固化在硬件里。BootROM是掩膜ROM,出厂就写死了,改不了。HSM的密钥存储区有专门的保护机制,主核读不到。CMAC运算由HSM的硬件加速引擎完成,不占用主核资源。这三个特性加在一起,构成了一个从硬件到软件的完整信任链。
2.2 CMAC相比其他MAC算法的取舍
选CMAC而不是HMAC-SHA256,主要基于三点考虑。
第一是硬件复用。HSM里通常已经集成了AES加速引擎,CMAC直接基于AES-128或AES-256实现,不需要额外的SHA引擎。HMAC-SHA256需要SHA-256硬件,如果HSM里没有这个引擎,就得用软件算,速度会慢很多。我实测过,在AURIX TC3xx上,HSM硬件CMAC算一个4KB的块大概需要80微秒,软件HMAC-SHA256需要将近2毫秒,差了25倍。
第二是标签长度可控。CMAC的输出长度等于分组密码的分组长度,AES-128就是128位,AES-256就是256位。你可以根据安全等级需求截取前64位或前96位,灵活适配不同的存储和传输约束。HMAC-SHA256固定输出256位,没得选。
第三是标准化程度高。CMAC是NIST SP 800-38B标准算法,也是ISO 21434推荐的固件完整性校验算法之一。在功能安全审计的时候,用标准算法比用自定义算法好过得多。
当然CMAC也不是没有缺点。它需要收发双方共享同一个密钥,属于对称加密体系。在OTA场景下,如果每辆车的密钥都一样,一辆车被破解就意味着所有车都被破解。所以实际项目中,我们通常采用一车一密的方案,每辆车的CMAC密钥在产线注入时由HSM的真随机数发生器生成,或者由后端KMS派生。这样即使某辆车的密钥泄露,也不会影响其他车辆。
2.3 信任链的层级设计
信任链的层级不是越多越好,层级越多启动越慢,但层级太少又不够灵活。我一般推荐三级信任链设计。
第一级是BootROM,这是硬件信任根,不可修改。它包含一个固化的公钥哈希,用来验证Bootloader的签名。BootROM的代码在芯片流片时就固化了,任何外部手段都改不了。
第二级是Bootloader,这是可更新的,但更新必须经过签名验证。Bootloader负责初始化HSM、加载CMAC密钥、验证Application的完整性。Bootloader通常还包含OTA升级的逻辑,所以它是整个安全启动里最复杂的部分。
第三级是Application,这是实际业务逻辑。Application的CMAC标签在编译后由后端签名服务器生成,和固件一起打包。Bootloader在跳转前验证这个标签,验过了才跳转。
有些项目还会加第四级,比如把标定数据、配置参数也纳入验证范围。这个看具体需求,如果标定数据被篡改会影响安全功能,那就必须验。如果只是舒适性配置,可以放宽。
3. 核心细节解析:HSM密钥管理与CMAC计算全流程
3.1 HSM密钥的生成、注入与存储
密钥管理是整个安全启动里最容易出问题的环节。我见过太多项目在密钥管理上翻车,要么是密钥在产线泄露了,要么是密钥更新的时候把设备变砖了。
密钥生成必须在HSM内部完成。以Infineon AURIX的HSM为例,它有一个真随机数发生器,符合AIS-31标准。你可以通过HSM的固件接口调用密钥生成命令,生成的密钥直接存在HSM的密钥槽里,永远不会以明文形式出现在HSM外部。如果你用的是NXP S32K的CSEc模块,也是类似的机制,密钥生成和存储都在安全硬件内部完成。
密钥注入在产线环节完成。产线工控机通过安全通道把密钥派生因子发给HSM,HSM内部用KDF派生出最终的CMAC密钥。这个过程要注意几点:产线工控机本身必须是可信的,不能中病毒;通信通道必须加密,防止中间人截获;注入完成后要立即断开调试接口,防止后续被读取。
密钥存储方面,HSM通常提供多个密钥槽,每个槽可以配置不同的访问权限。CMAC密钥一般存在只能用于CMAC计算的槽里,不允许导出、不允许读取。有些HSM还支持密钥的原子更新,更新过程中断电不会导致密钥损坏,这个特性在OTA场景下非常重要。
注意:密钥注入后一定要做回读测试,确认密钥写入成功且不可读出。我遇到过HSM密钥槽写入失败但没报错的情况,结果设备出厂后安全启动直接失效,召回成本极高。
3.2 CMAC标签的生成与校验流程
CMAC的计算流程分两步:子密钥生成和消息认证码计算。
子密钥生成基于AES密钥。先用AES密钥加密全零块,得到L。然后根据L的最高位判断,如果最高位是0,K1 = L << 1;如果最高位是1,K1 = (L << 1) XOR Rb,其中Rb对于128位分组是0x87。K2同理,由K1再推导一次。
消息认证码计算分三种情况。如果消息长度是分组长度的整数倍且不为零,最后一个块先异或K1再加密。如果消息长度不是整数倍,先填充10*到分组长度,再异或K2再加密。如果消息为空,直接加密K2。最终输出的密文就是CMAC标签。
在HSM里,这些计算都是硬件自动完成的。你只需要调用HSM的CMAC接口,传入密钥槽编号、数据指针、数据长度,HSM返回计算出的标签。以AURIX HSM为例,接口大概长这样:
// 伪代码示意,具体接口参考芯片手册 hsm_cmac_params_t params; params.key_slot = CMAC_KEY_SLOT_0; params.data = firmware_buffer; params.data_len = firmware_len; params.mac_out = cmac_output; hsm_cmac_compute(¶ms);校验的时候,Bootloader先把Flash里的固件读出来,调用HSM算出CMAC,然后和固件末尾附带的CMAC标签做比较。比较操作必须在HSM内部完成,或者用恒定时间比较算法,防止时序攻击。如果两个标签一致,说明固件完整且真实,可以跳转。如果不一致,进入安全失败处理流程。
3.3 安全失败处理策略
验签失败之后怎么办?这个问题比验签本身更重要。我见过有的项目验签失败后直接死循环,结果车在高速上突然熄火,这是绝对不能接受的。
安全失败处理要分场景设计。对于安全关键ECU,比如刹车控制器,验签失败后应该进入降级模式,用最后已知良好的固件运行,同时点亮故障灯,记录DTC,限制车辆最高速度。对于非安全关键ECU,比如车窗控制器,验签失败后可以进入安全停机模式,只响应基本的诊断请求,不执行任何业务逻辑。
还有一种情况是首次启动失败。产线上刚刷完固件,第一次启动验签就失败了,这时候应该允许通过调试接口重新刷写,但必须记录安全事件。如果连续多次验签失败,应该锁定调试接口,防止暴力破解。
实操心得:安全失败处理的状态机一定要在Bootloader里实现,不能放在Application里。因为Application本身可能就是被篡改的对象,如果验签失败还跳转到Application去处理失败逻辑,等于把控制权交给了攻击者。
4. 实操过程:从BootROM到Application的完整实现
4.1 环境准备与工具链配置
我以Infineon AURIX TC3xx系列为例,把整个实操流程走一遍。你需要准备以下工具:
- AURIX Development Studio或Tasking编译器
- Infineon HSM固件包(通常由芯片厂商提供)
- 硬件安全模块配置工具(如Infineon的HSM Configuration Wizard)
- 调试器(如Lauterbach TRACE32或PLS UDE)
- 后端签名服务器(可以用OpenSSL搭建测试环境)
工具链配置的关键是HSM固件和主核固件的分离编译。HSM固件是独立编译的,生成一个二进制文件,通过主核的Flash编程接口烧录到HSM的Flash区域。主核固件编译时,链接脚本要把Bootloader和Application分开,Bootloader放在Flash起始地址,Application放在后面。
链接脚本里还要预留CMAC标签的存储空间。我一般把CMAC标签放在Application固件的末尾,占32字节(AES-256的CMAC输出是32字节)。Bootloader在验签时,先读Application的代码段,算出CMAC,再读末尾的32字节标签做比较。
4.2 BootROM阶段的信任根验证
BootROM是芯片上电后执行的第一段代码,它在掩膜ROM里,不可修改。BootROM的主要工作是:
- 初始化HSM,加载HSM固件
- 从Flash的固定地址读取Bootloader的CMAC标签
- 调用HSM计算Bootloader的CMAC
- 比较两个标签,一致则跳转到Bootloader,不一致则进入安全失败处理
BootROM的验证逻辑是芯片厂商固化好的,你改不了,但你可以配置一些参数,比如Bootloader的起始地址、CMAC标签的存储位置、验签失败后的行为。这些配置通常通过芯片的Option Bytes或OTP区域设置。
注意:BootROM的配置一旦写入OTP就不可更改,所以量产前一定要在工程样片上反复验证。我见过一个项目因为OTP配置写错,导致所有样片变砖,损失惨重。
4.3 Bootloader阶段的HSM初始化和Application验签
Bootloader是安全启动里你能控制的最底层代码。它主要做四件事:
第一,初始化HSM。BootROM虽然加载了HSM固件,但HSM的完整功能可能还需要Bootloader来初始化,比如配置密钥槽、使能CMAC引擎、设置访问权限。
第二,加载CMAC密钥。如果密钥是产线注入的,Bootloader只需要从HSM密钥槽里读取密钥句柄。如果密钥是派生出来的,Bootloader需要调用HSM的KDF接口,用主密钥派生出CMAC密钥。
第三,验证Application。Bootloader从Flash里读取Application的代码段,调用HSM计算CMAC,和存储的标签做比较。这里要注意读取顺序,必须按地址从低到高顺序读取,不能跳读,否则CMAC计算结果会对不上。
第四,跳转到Application。验签通过后,Bootloader要正确设置堆栈指针、中断向量表、时钟配置,然后跳转到Application的入口地址。跳转前要关闭所有中断,跳转后再由Application重新使能。
// Bootloader验签伪代码 uint8_t app_cmac[32]; uint8_t stored_cmac[32]; // 从Flash读取存储的CMAC标签 flash_read(APP_CMAC_ADDR, stored_cmac, 32); // 调用HSM计算Application的CMAC hsm_cmac_params_t params; params.key_slot = CMAC_KEY_SLOT_0; params.data = (uint8_t*)APP_START_ADDR; params.data_len = APP_SIZE; params.mac_out = app_cmac; hsm_cmac_compute(¶ms); // 恒定时间比较 if (constant_time_compare(app_cmac, stored_cmac, 32) == 0) { // 验签通过,跳转到Application jump_to_application(APP_START_ADDR); } else { // 验签失败,进入安全失败处理 security_failure_handler(); }4.4 Application阶段的运行时完整性校验
Application跑起来之后,安全启动的任务就结束了吗?没有。攻击者可能在运行时通过调试接口或者漏洞注入恶意代码,所以Application还需要做运行时完整性校验。
我通常的做法是在Application里开一个低优先级的任务,定期(比如每100ms)对关键代码段做CMAC校验。校验的范围包括中断向量表、安全关键函数、标定数据。如果校验失败,立即进入安全状态,限制车辆功能。
运行时校验的频率要权衡。太频繁会影响CPU负载,太稀疏又可能被攻击者利用时间窗口。我的经验是,对于安全关键ECU,校验周期不超过100ms;对于非安全关键ECU,1秒一次就够了。
还有一个技巧是随机化校验地址。攻击者可能通过分析校验模式来定位校验点,然后针对性地绕过。你可以在每次校验时随机选择起始地址和校验长度,让攻击者无法预测。
5. 常见问题与排查技巧实录
5.1 CMAC验签失败的五大原因
在实际项目中,CMAC验签失败是最常见的问题。我把踩过的坑整理成一张速查表:
| 故障现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 首次启动就失败 | 密钥注入错误 | 回读HSM密钥槽状态 | 重新注入密钥 |
| 首次启动就失败 | CMAC标签生成时数据范围不对 | 对比签名服务器和Bootloader的数据范围 | 统一数据范围定义 |
| OTA后启动失败 | 固件传输过程中数据损坏 | 对比OTA前后固件的哈希值 | 增加传输校验重传机制 |
| 偶发性启动失败 | HSM初始化时序问题 | 用调试器抓HSM初始化流程 | 增加HSM就绪等待 |
| 特定批次失败 | Flash读取时序问题 | 检查Flash等待周期配置 | 调整Flash控制器时序 |
我第一次做安全启动的时候,遇到过一个特别隐蔽的问题:签名服务器生成CMAC时用的数据范围包含了固件末尾的CMAC标签本身,而Bootloader验签时只读了代码段,没读标签。结果就是签名服务器算出来的CMAC和Bootloader算出来的永远对不上。这个问题查了整整两天,最后用二进制对比工具逐字节比对才发现。
5.2 HSM通信超时的排查思路
HSM和主核之间的通信通常通过共享内存或邮箱机制。如果HSM忙或者通信配置不对,主核调用HSM接口时会超时。
排查步骤是这样的:先用调试器看HSM的状态寄存器,确认HSM是否已经启动完成。如果HSM没启动,检查HSM固件是否烧录成功、时钟是否使能。如果HSM启动了但通信超时,检查共享内存的地址配置、中断配置、以及HSM的访问权限设置。
我遇到过一次HSM通信超时,原因是主核和HSM对共享内存的地址映射理解不一致。主核以为共享内存映射在0x80000000,HSM以为映射在0x90000000,两边读写的是不同的物理内存。后来对照芯片手册的存储映射表才发现问题。
实操心得:HSM通信调试一定要用调试器同时抓主核和HSM的寄存器状态,单看一边很难定位问题。Lauterbach的TRACE32支持多核同时调试,调HSM的时候特别有用。
5.3 安全启动对启动时间的影响与优化
安全启动会增加启动时间,这是不可避免的。CMAC计算需要时间,HSM初始化需要时间,Flash读取也需要时间。我实测过,一个2MB的Application,用HSM硬件CMAC验签大概需要40毫秒,加上HSM初始化和Flash读取,总共增加约60毫秒启动时间。
对于大多数ECU来说,60毫秒可以接受。但如果你的ECU有快速启动需求,比如倒车影像控制器,要求上电后200毫秒内出图,那60毫秒就有点紧张了。
优化手段有几个:一是并行验签,Bootloader在验Application的同时,Application可以先初始化显示相关的硬件,等验签通过后再使能显示输出。二是分块验签,把Application分成多个块,优先验签启动必需的关键块,非关键块延后验签。三是缓存验签结果,如果上次启动验签通过且固件没有更新,可以跳过验签直接启动,但这种方式会降低安全性,慎用。
5.4 密钥更新与设备变砖的预防
密钥更新是安全启动里风险最高的操作。如果更新过程中断电,或者新密钥写入失败,设备可能永远无法启动。
预防措施有三条。第一,双密钥槽备份。HSM通常支持多个密钥槽,你可以把当前密钥存在槽0,更新时先写到槽1,验证槽1可用后再切换。如果更新失败,槽0还在,设备不会变砖。
第二,原子更新。有些HSM支持密钥的原子更新,更新过程中断电,HSM会自动回滚到更新前的状态。这个特性一定要用上。
第三,恢复模式。Bootloader里要预留一个恢复模式,当所有密钥槽都不可用时,允许通过调试接口重新注入密钥。恢复模式要有额外的认证机制,比如挑战应答,防止被滥用。
我见过一个项目因为密钥更新失败导致设备变砖,最后只能把ECU拆下来返厂,用编程器重新烧录。所以密钥更新方案一定要在实验室里反复测试断电场景,确认不会变砖再上量产。
6. 安全启动的扩展思考与实战建议
6.1 安全启动与OTA升级的配合
安全启动和OTA升级是相辅相成的。OTA升级的固件包必须包含CMAC标签,Bootloader在刷写前先验签,验过了才写入Flash。写入完成后,Bootloader再验一次Flash里的固件,确认写入无误后才跳转。
OTA升级还有一个特殊场景是差分升级。差分升级只传输新旧固件的差异部分,可以减少传输数据量。但差分升级的验签比较麻烦,因为差分算法本身可能被攻击者利用。我的建议是,差分升级后先还原出完整固件,再对完整固件做CMAC验签,不要对差分数据直接验签。
6.2 安全启动的认证与合规
如果你做的ECU要过ISO 21434认证,安全启动是必查项。认证机构会检查你的信任根是否固化、密钥管理是否合规、验签失败处理是否安全、有没有防回滚机制。
我建议在项目早期就把安全启动的方案文档化,包括威胁模型分析、信任链设计、密钥管理流程、安全失败处理策略。这些文档在认证的时候直接提交,可以省很多事。
另外,EVITA认证对HSM的安全等级有明确要求。Full EVITA要求HSM支持安全启动、安全通信、安全存储,Medium EVITA要求支持安全启动和安全通信。选芯片的时候要确认HSM的认证等级是否满足项目需求。
6.3 给新手的三个实战建议
第一个建议是先跑通再优化。安全启动涉及BootROM、HSM、Bootloader、Application多个环节,一开始不要追求最优方案,先用最简单的配置把整条链路跑通,确认每个环节都能正常工作,再逐步优化性能和安全性。
第二个建议是做好版本管理。安全启动的代码和密钥一定要严格版本管理。我见过一个项目因为密钥版本和固件版本不匹配,导致OTA后设备无法启动。后来我们在固件头里增加了密钥版本号,Bootloader根据版本号选择对应的密钥槽,问题才解决。
第三个建议是多做破坏性测试。安全启动的测试不能只测正常流程,要重点测异常流程:验签失败、密钥损坏、Flash读取错误、HSM通信超时、OTA过程中断电。这些异常场景才是安全启动真正要防的。
我个人在实际操作中的体会是,安全启动最难的不是技术实现,而是流程管理。密钥怎么生成、怎么注入、怎么更新、怎么销毁,每一个环节都需要严格的流程控制。技术方案可以抄,流程管理抄不来,必须结合自己团队的实际情况来设计。最后再分享一个小技巧:在Bootloader里加一个安全启动的自检命令,产线测试的时候可以通过诊断接口触发自检,确认安全启动功能正常,这样可以在出厂前就发现问题,避免召回风险。