车规MCU安全启动、SecOC通信与部件保护落地解析
2026/9/19 17:42:34 网站建设 项目流程

简介:这份文档面向从事汽车电子设计、开发与维护的工程师,尤其是关注车载信息安全的从业者,系统梳理了车载MCU层面的信息安全防护思路。内容围绕三条主线展开:借助加密服务引擎CSE与AES-128算法校验Bootloader完整性与真实性的安全启动机制;以随机数配合对称加密保障CAN网络消息保密性与完整性的安全通信方案;以及通过随机数与ECU自身ID双重比对来防范部件被非法替换的部件保护手段,并配有流程示意图帮助理解各环节的校验逻辑。资源包内共1个docx文档,约2.45MB,阅读时建议具备一定的汽车电子与网络安全基础,重点关注各机制的工作原理与实际落地方式。目前已有91人学习,适合用于项目实践参考或企业级安全策略的制定。

1. 车载电子电器架构里,MCU 的安全边界在哪

一辆车上几十到上百个 ECU,从分布式走到域控、再走到区域控制器加中央计算,MCU 始终在最末端干实事:采样、驱动、执行。功能上收得越紧,安全上就越危险——攻击者拿到一个 CAN 帧或者一段诊断报文,就能让刹车灯常亮、让车窗乱动,甚至给整车刷进一份伪造固件。标题里的三件事其实是一条链:安全启动保证上电后跑的是自家代码,通信安全保证车上的报文没被别人改过,部件保护保证代码和密钥不会被读走、调走。做 MCU 开发的人迟早会撞上这三块,信息安全工程师要评的也是这三块。下面按落地顺序把每一块的机制、参数和坑拆开讲。

2. 车规 MCU 安全启动:从信任根到镜像校验

2.1 信任链的起点:BootROM、HSM 与 OEM 根密钥

安全启动的本质是一条信任链:上电后第一段代码必须不可改,它再去验第二段,逐级往下。车规 MCU 的常见做法是把第一段固化在芯片的 BootROM 里,只读、不可擦写,芯片出厂即固定。BootROM 里带一段最小的校验逻辑,直接操作片内的安全子系统——英飞凌、恩智浦、瑞萨这些厂商的常见叫法是 HSM(Hardware Security Module),也有按 SHE(Secure Hardware Extension)规范实现的,两者都提供密钥槽、加解密和 MAC 运算,区别在于 HSM 可编程能力更强,SHE 更偏固定功能。

根密钥从哪来?芯片里通常有一次性可编程区域(OTP / eFuse),产线刷写阶段把 OEM 根密钥或密钥派生种子烧进去,之后再想读出来就只剩一串错误码。这一步是整个链条的地基,键一旦灌错了,后面所有校验都白做,所以产线上一般要求刷写后立刻做一次回读校验,且回读走的是校验路径而不是读取路径。

信任链的层级大致是:BootROM → 二级引导(Bootloader)→ 应用(App)。有的项目还会把 HSM 自身的固件放在最前,因为它要参与后面的 MAC 计算。每一级的公钥或对称密钥都由上一级持有,这样任何一层被替换都会在下一级启动时暴露。

2.2 镜像头结构:安全启动校验需要哪些字段

校验不是把整个 Flash 跑一遍 CRC 那么简单,需要一份约定好的镜像头。头部字段少了会缺防回滚能力,多了会拖慢启动。下面是一份常见的头部定义:

/* 镜像头:固定 32 字节,紧贴镜像体,放在分区起始地址 */ typedef struct { uint32_t magic; /* 0x5A5A1234,快速判定分区内是否为有效镜像 */ uint32_t img_len; /* 镜像体长度(字节),不含头部本身 */ uint32_t img_ver; /* 版本号,单调递增,用于防版本回滚 */ uint32_t reserved; /* 对齐占位,也常放目标 ECU 标识 */ uint8_t cmac[16]; /* 头部前 16 字节 + 镜像体的 AES-CMAC 结果 */ } img_header_t;

magic用于上电时快速筛掉空白 Flash;img_len决定校验范围,写错就会把相邻分区算进来,验证必然失败;img_ver是防回滚的唯一依据,BootROM 或 Bootloader 里保存一个当前最低版本,低于它就拒绝启动,否则攻击者可以拿一份有漏洞的旧固件刷回去;cmac是整段内容的完整性标签,用 HSM 里的密钥槽算出来。

校验逻辑用一句伪代码概括就是:把头部前 16 字节和镜像体拼起来,交给 HSM 做 AES-CMAC,结果和头部里的cmac逐字节比对,一致才跳转。整个过程中明文密钥不出 HSM,主核只能拿到"通过/不通过"。

2.3 用 AES-CMAC 验签的最小实现

主核侧调 HSM 的封装一般长这样,不同厂商的寄存器名不同,但接口形态高度相似:

/* 主核侧调用 HSM 计算 CMAC,密钥句柄由 HSM 内部索引,主核看不到明文 */ int secure_boot_verify(uint32_t part_addr) { img_header_t *hdr = (img_header_t *)part_addr; uint8_t calc[16]; int ret; if (hdr->magic != 0x5A5A1234u) { return BOOT_ERR_MAGIC; /* 分区不是有效镜像 */ } if (hdr->img_len == 0 || hdr->img_len > MAX_IMG_LEN) { return BOOT_ERR_LEN; /* 长度越界,直接判失败 */ } /* 密钥槽 1 = 镜像校验密钥,由产线一次性灌入,此后不可读写 */ ret = hsm_cmac(HSM_KEY_SLOT_IMG, part_addr, 16 + hdr->img_len, calc); if (ret != 0) { return BOOT_ERR_HSM; /* HSM 通信异常,不要当成校验失败 */ } if (memcmp(calc, hdr->cmac, 16) != 0) { return BOOT_ERR_CMAC; /* 内容被篡改或烧写不完整 */ } if (hdr->img_ver < boot_min_version()) { return BOOT_ERR_ROLLBACK; /* 版本低于防回滚下限 */ } return BOOT_OK; }

三点说明。第一,校验范围是16 + img_len,头部后 16 字节的cmac自己不参与计算,否则就成了自引用。第二,HSM 调用失败要区分于校验不通过,前者可能是硬件异常或时钟没使能,排查方向完全不同,日志里必须分开记录。第三,版本比较放在 MAC 校验之后,因为版本号本身也受 MAC 保护,先验 MAC 再信版本号,顺序反了等于给攻击者留了个绕过点。

产线侧要做一次预计算,把 CMAC 值填进头部再刷写,用 Python 就能验证工具链和固件端算法是否一致:

from Crypto.Hash import CMAC from Crypto.Cipher import AES def build_header(body: bytes, key: bytes, ver: int) -> bytes: # 前 16 字节:magic / 长度 / 版本 / 保留 prefix = (0x5A5A1234).to_bytes(4, 'little') \ + len(body).to_bytes(4, 'little') \ + ver.to_bytes(4, 'little') + b'\x00' * 4 # CMAC 覆盖头部前 16 字节 + 镜像体,不含自己的 16 字节 c = CMAC.new(key, ciphermod=AES) c.update(prefix + body) return prefix + c.digest() + body

key要和 HSM 密钥槽里的值一致;ver与防回滚下限规则一致;输出顺序按固件端结构体布局,小端还是大端必须两边对齐,这类字节序错配在联调时表现为"每次都校验失败",很难从错误码看出来。

2.4 A/B 分区与回滚:启动失败的三级降级

单分区方案一旦校验失败就是变砖,量产项目基本都会上 A/B 两份镜像。必要参数有三个:分区切换标志存在的 NVM 位置、回滚尝试计数上限、以及"确认可启动"的判定条件。

启动流程常见是:先验当前活动分区,失败则切到备份分区并给计数器加一;备份分区也失败,进入恢复模式(通常是通过 CAN 或以太网接收新固件);计数器超过上限就锁死在恢复模式,不允许在两份坏镜像之间反复弹跳。计数器阈值一般设 3 到 5 次,设太小会被偶发的擦写不良误判,设太大则可能把电池耗干。

配合这套机制,应用起来后要做一件容易被忘掉的事:在规定时间内主动把新分区标记为"可用"。这一步是把"能启动"和"能正常工作"区分开——只看 MAC 通过是不够的,应用自己起不来、外设初始化挂了,下次上电还得回滚。

2.5 生命周期状态:调试口和启动阶段的联锁

安全启动能不能被绕开,很大程度看调试口。车规 MCU 一般有几档生命周期状态,比如开发、量产、返修,状态只能单向往后推,靠 OTP 位锁定。开发状态下 SWD/JTAG 全开,方便调试;切到量产状态时同时完成两件事:关闭调试口,锁死 OTP 里与调试相关的配置位。

注意顺序:先切量产状态,再灌根密钥。反过来做的话,密钥已经写进去了,调试口还开着,等于把钥匙贴门上。返修场景下如果要保留调试能力,常见做法是留下一路受认证的服务接口,靠挑战应答放行,而不是把物理口重新打开。

3. 车载 MCU 通信安全:SecOC 报文认证怎么落地

3.1 SecOC 的报文结构:Authentic PDU 里多出来的几个字节

总线上一条普通 CAN 帧只有 ID 和数据,任何接入者都能仿造。AUTOSAR 里对应的方案是 SecOC,思路是把原始 PDU 加上认证信息再发出去,接收端算一遍比对。认证信息由两部分组成:截断的 MAC(Authenticator)和新鲜度值(Freshness Value)。

字段典型长度说明
Payload与原 PDU 相同业务数据本身,不加密也可认证
Authenticator3~8 字节AES-CMAC 截断结果,越短越省带宽、越弱
Freshness Value1~8 字节抗重放,可按单调计数器或时间同步
SecOC 头部0~2 字节携带 FV 长度、截断标识等元信息

CAN-FD 每帧最多 64 字节,截断长度可以放宽到 8 字节;经典 CAN 只有 8 字节,很多项目被迫截断到 3 字节甚至更短,安全性打折扣。这也是为什么新架构里加密报文更倾向走 CAN-FD 或以太网。截断长度不是能随便选的数值,它和可容忍的伪造成功率直接相关,通常由整车的信息安全需求分析反推出来。

3.2 新鲜度值同步:最容易出问题的地方

MAC 只能证明报文内容没被改过,不能防重放——攻击者把一条合法的"解锁"报文原样再发一次,接收端算出的 MAC 完全正确。新鲜度值就是补这个洞的,它必须每次不同且接收方能判断是否递增。

常见有两种方案。一是纯计数器,发送端每发一帧加一,接收端维护一份期望值,允许窗口内的偏差;问题是 ECU 断电重启后计数器要从 NVM 恢复,恢复策略没设计好就会出现"上电后前十帧全丢"。二是主从同步,总线上有一路专用的同步报文周期广播 FV,其他节点跟着走;风险是同步报文自己如果被干扰,全网跟着失步。

失步后的处理有讲究。我一般会要求接收端保留一个容错窗口而不是只接受严格相等的值:窗口外的报文直接丢并计数,计数超过阈值就把该 PDU 标为不可信,触发上层降级,而不是静默接受。这个阈值要配合整车诊断记录,否则线上出现批量丢帧时根本查不到源头。

3.3 生成 Authenticator 的代码与参数

发送侧的核心操作就是把数据、密钥、新鲜度值拼起来做 CMAC 再截断:

/* SecOC 发送侧:计算 Authenticator 并组装认证 PDU */ uint8_t secoc_build_frame(const uint8_t *pdu, uint8_t pdu_len, uint64_t fv, uint8_t *out) { uint8_t mac[16]; uint8_t fv_bytes[8]; uint8_t mac_len = SECOC_TRUNC_LEN; /* 项目配置,3~8 字节 */ /* FV 按大端放进 MAC 输入,和接收端必须完全一致 */ for (int i = 0; i < 8; i++) { fv_bytes[i] = (uint8_t)(fv >> (56 - i * 8)); } /* 输入 = 数据 || FV,密钥槽 3 为 SecOC 认证密钥 */ hsm_cmac_start(HSM_KEY_SLOT_SECOC); hsm_cmac_update(fv_bytes, 8); hsm_cmac_update(pdu, pdu_len); hsm_cmac_finish(mac); /* PDU 有效数据按位打包在前,Authenticator 取 MAC 高位截断 */ pack_bits(out, pdu, pdu_len); memcpy(out + ((pdu_len + 7) / 8), mac, mac_len); return (uint8_t)(((pdu_len + 7) / 8) + mac_len); }

参数上要注意三处。SECOC_TRUNC_LEN必须两边配置一致,差一字节就是永久性认证失败。FV 的字节序和拼接顺序(FV 在前还是数据在前)属于同一类问题,通常由 AUTOSAR 配置工具生成,改一个地方就要重新导出。密钥槽选择也要统一,同一辆车不同 ECU 之间一般用不同的密钥或不同的密钥派生路径,避免一个节点泄露导致全网失效。

CAN 上的数据是按位排列的,pack_bits这类按位拷贝不能用手写的memcpy代替——两个字节的业务数据可能只占 12 位,剩下 4 位是填充,填充位对齐错误在实际抓包里看不出来,只在特定数据值上偶发失败。

3.4 密钥注入:产线和售后怎么把密钥灌进去

密钥不能跟着固件一起刷,固件是可复制的。常见做法是产线上一道单独的密钥注入工序:诊断仪或刷写工具通过安全通道和 ECU 建立会话,把密钥加密传输,ECU 侧解密后写进 HSM 密钥槽或受保护的 NVM 区域。写完必须回读校验,但回读的是"能否用该密钥算出正确 MAC",而不是把密钥读出来比对。

产线上还有一条铁律:密钥注入必须在生命周期切到量产状态之后完成。售后换件时,备件 ECU 通常是空密钥状态,需要走一套受权限控制的注入流程,这个流程本身也要有审计记录。

4. 部件保护:Flash、调试口与密钥存储

4.1 Flash 分区与存储保护单元

部件保护的第一层是别让人把代码读走。车规 MCU 普遍有存储保护单元,可以按地址区间设置读、写、执行权限。典型划分是:Bootloader 区只允许执行不允许读,应用区允许执行和读,标定参数区允许读不允许执行,密钥区任何主核访问都不允许,只有 HSM 能碰。

这里有个容易踩的坑:很多 MCU 的保护粒度是按块(block)或按扇区(sector)对齐的,边界没对齐可能出现"想保护的没保护上,不该保护的锁死了"。配置完后建议用调试器实际尝试读一次受保护区,确认返回的是总线错误而不是正常数据,否则说明配置没生效。

4.2 调试接口封锁与生命周期迁移

调试口是最直接的攻击面。封锁手段有几档:最简单的是一次性烧断调试使能位;更精细的是按生命周期状态分级,量产状态下物理口关闭,但保留一条需要认证的服务通道用于返修。

迁移过程本身要防掉电。OTP 位的写入如果中途断电,可能停在一个半开半合的状态。常见做法是在迁移前先把状态写入 NVM 并存一份镜像,上电时比对 OTP 与 NVM,不一致就走异常处理流程报错,而不是继续往下启动。这一步的日志要落到非易失区域,方便售后定位。

4.3 密钥放哪:SHE 密钥槽与 NVM 加密存储的取舍

密钥存储有两条路。一是有硬件安全模块的芯片,密钥直接进密钥槽,主核只能引用句柄,读不出来;二是不带 HSM 的低成本 MCU,只能把密钥加密后放 NVM,密钥的加密密钥再从别处派生,这种方案的理论安全性明显弱一档。

方式读取难度适用场景主要风险
SHE / HSM 密钥槽主核不可读安全启动、SecOC槽位数量有限
NVM 加密存储可读但为密文成本敏感的从节点派生密钥泄露即全失
OTP 直存不可改、不可读根密钥、UID写错无法返工

选型建议是:参与安全启动和报文认证的密钥走硬件密钥槽;纯粹用于本地数据的密钥可以放 NVM;根密钥或芯片唯一标识走 OTP。同一颗芯片上混用也要保证不出现"高价值密钥放在低保护等级区域"的情况。

4.4 诊断安全访问与 Seed & Key 流程

诊断服务里有一组安全访问服务,用来在上位机和 ECU 之间建一个受控会话。它的典型流程是:上位机请求种子,ECU 返回一个随机数,上位机用约定算法算出应答,ECU 验证后解锁。

/* ECU 侧:种子生成与应答校验,密钥以常量形式存在受保护区 */ uint8_t diag_seed[4]; uint8_t diag_try_count; int diag_request_seed(uint8_t *out) { if (diag_try_count >= DIAG_MAX_TRY) { return DIAG_ERR_LOCKED; /* 失败次数超限,进入延时锁定 */ } /* 用 HSM 的随机源,不要用软件 rand,否则种子可预测 */ return hsm_random_bytes(diag_seed, sizeof(diag_seed)); } int diag_verify_key(const uint8_t *key) { uint8_t expect[4]; /* 种子与密钥做带密钥的散列,具体算法按 OEM 规范,常见是 AES 或 CMAC */ hsm_kdf_response(diag_seed, HSM_KEY_SLOT_DIAG, expect); if (memcmp(expect, key, 4) != 0) { diag_try_count++; return DIAG_ERR_DENIED; } diag_try_count = 0; /* 成功即清零,避免正常操作被误锁 */ return DIAG_OK; }

两个参数必须处理:失败次数上限和成功后清零。次数不设或设得太大,攻击者可以离线爆破;成功后不清零,维修人员操作几次就把 ECU 锁死了。种子必须来自硬件随机源,软件伪随机在复位后可能产生相同序列,等于把种子变成固定值。

5. 安全措施的验证:怎么证明它真的生效

写完不等于生效,MCU 安全里最容易被忽略的是验证环节。常见验证手段集中在三块:功能验证、故障注入和侧信道。

功能验证解决的是逻辑对不对。安全启动这一块要覆盖的用例包括:正常镜像能否启动、篡改一个字节后能否被拒、低版本镜像能否被防回滚规则拦住、A/B 切换是否按计数上限收敛、断电重启后计数器能否正确恢复。SecOC 要覆盖的正常帧放过、篡改帧拒绝、重放帧拒绝、失步后重同步。这些用例最好固化成产线或台架脚本,每次改配置都跑一遍。

故障注入解决的是"逻辑对了,物理上会不会被绕过"。常见方式是给 MCU 的供电或时钟加极短毛刺,看校验逻辑会不会被跳过或跳错分支。做这类测试要盯三件事:失败时芯片停在什么状态、是否出现未认证代码执行、日志能不能记录下来。有一条经验值得记住:故障注入往往在 MAC 比对那一步找缝隙,所以比对逻辑本身最好用定长比较、不提前返回,减少可被精确命中的分支。

侧信道解决的是"密钥会不会从功耗或电磁里漏出来"。对 MCU 来说,AES 运算期间的功耗曲线如果和密钥强相关,理论上可以被统计分析出来。缓解手段一般是硬件层的掩码和随机化,对开发者来说能做的是确认使用的密钥槽开启了这些保护特性,并在选型阶段把这项能力列为必选项,而不是等出了问题再补。

一个可以直接用的验证清单长这样。

验证项手段通过判据
镜像篡改改一个字节后启动拒绝启动并进恢复模式
版本回滚烧旧版本镜像拒绝启动并记录版本错误码
调试口封锁量产态下接调试器连接被拒或总线报错
报文重放重发已采集的 SecOC 帧接收端丢弃并计数
密钥读取主核直接读密钥区触发总线异常,无数据外泄
掉电恢复校验中断电再上电状态一致,无半开状态

最后补一个实操细节:这些验证用例跑完之后,日志要落到独立于被测固件的区域,否则一旦被测镜像本身有问题,日志也跟着没了。我一般会要求把关键拒绝事件同时写进 HSM 侧的受保护区域和整车诊断事件,两边对得上才算一次可信的验证记录。

本文还有配套的精品资源,点击获取

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

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

立即咨询