车规级 MCU Flash 烧录:通过 UDS 协议实现安全密钥加密写入流程
车规 MCU(如 NXP S32K344、Infineon TC3xx、Renesas RH850)的 Flash 不能随便刷——ASIL-B/D 要求"未授权写入"必须拦在协议层。产线烧录、售后 OTA、4S 店诊断仪刷写,走的都是 UDS(ISO 14229) 的 34/36/37 三段式(RequestDownload → TransferData → RequestTransferExit),但在这之前必须先过 27 SecurityAccess(安全访问) 这一关:ECU 甩 Seed(随机数),诊断仪用 OEM 私有算法算 Key 回喂,验过才能碰 Flash。下面以 TC377 + 野村 UDS 栈为例,走完密钥加密写入全流程。
一、为什么不能直接 $34
裸刷的 $34 服务(RequestDownload)在车规 MCU 里默认是"拒绝状态"——Flash 驱动(FBL,Flash Boot Loader)在擦写前会查 SecurityLevel:
• 默认会话(Default Session,$10 01):只能读 DID、读 DTC,写 Flash → NRC 0x33(SecurityAccessDenied)
• 扩展会话(Extended Session,10 03):能进编程会话,但 Flash 仍锁,需先 27 解锁
• 编程会话(Programming Session,10 02):刷写主战场,但 27 不过照样写不了
ASIL-D 还要求:连续 3 次 Key 错 → 延时计数器升档(1s → 10s → 100s),防暴力破 Seed/Key。
二、$27 服务的 Seed→Key 流程
27 分两子服务:27 01 请求 Seed,$27 02 发 Key。
/* FBL 内 $27 处理伪代码(TC377)*/
uint8 seed[4]; // ECU 生成的随机 Seed
uint8 fail_cnt = 0;
uint32 delay_ms = 0;
void uds_27_handler(uint8* req, uint8 len) {
if (req[1] == 0x01) { // 请求 Seed
if (delay_ms) { // 失败冷却中
send_nrc(0x37); // RequestSequenceError
return;
}
generate_random(seed, 4); // HSM/RNG 生成
send_resp(0x67, 0x01, seed, 4);
}
else if (req[1] == 0x02) { // 发 Key
uint8 key[4];
calc_key(seed, key); // ECU 端用同算法算 Key
if (memcmp(key, &req[2], 4) == 0) {
security_level = LEVEL_01; // 解锁
fail_cnt = 0;
send_resp(0x67, 0x02);
} else {
fail_cnt++;
delay_ms = 1000 * (1 << (fail_cnt - 1)); // 1/2/4/8... s
send_nrc(0x35); // InvalidKey
}
}
}
诊断仪侧(uds_27_02 发 Key 前)要做的是:拿 Seed → 用 OEM 给的 DLL/算法算 Key → 发 $27 02+Key。算法 OEM 私有,常见三种:
1. AES-128-CMAC:Seed 作 nonce,OEM 给的根密钥(存在 HSM 的 NVM 或产线 KMS)算 CMAC,截 4 字节当 Key
2. 多项式异或:老派做法,Seed ^ Constant_A,循环移位 ^ Constant_B,简单但慢慢被 AES 替代
3. UID 派生:Seed + MCU Unique ID(锁芯片),同一 Seed 在不同 ECU 上 Key 不同,防重放
三、解锁后刷 Flash:34/36/$37
$27 02 返回 $67 02 后,SecurityLevel=01,才能走刷写三部曲:
/* 诊断仪侧刷写流程 */
send("$10 03"); // 进扩展会话(保持 $3E 心跳)
send("$27 01"); // 取 Seed
seed = recv()[2..5];
key = oem_algo(seed); // DLL 算 Key
send("$27 02" + key); // 解锁
send("$10 02"); // 进编程会话(FBL 接管)
send("$34 00 44 00 01 00 00 8000"); // RequestDownload:addr=0x80000000, len=4KB
send("$36 01" + data_4KB); // TransferData 第 1 帧
// ... 多帧 $36
send("$37"); // RequestTransferExit
send("$31 01 FF 00"); // RoutineControl:CRC 校验整块
send("$11 01"); // ECU Reset,跳 App
几个关键点:
• $3E 服务(TesterPresent):扩展/编程会话里每 2–4 s 发一次,防会话超时(默认 5 s)掉回 Default
• 34 的 addr/len:车规 MCU 的 Flash 有 ECC,必须先 31 RoutineControl 擦(31 01 FF 01)再写,34 只是"建传输通道",$36 写时 FBL 内部调 FlashProgram()
• CRC 校验:写完必跑 $31 RoutineControl 调 FBL 内的 CRC32,比对原始 bin,错一字节都拒启动
四、产线密钥的两类分发
Seed/Key 算法 OEM 私有,但根密钥怎么进 MCU 有两种主流做法:
1. 产线 KMS 集中算(主流 Tier1 做法)
• MCU 出厂时空跑,产线首道工序:通过 JTAG/SWD 把 OEM 根密钥 + UID 派生参数 烧进 HSM 的 NVM(一次写,锁死)
• 后续诊断仪刷写时,Seed 由 ECU HSM 随机数出,诊断仪把 Seed 发 KMS 服务器,KMS 用根密钥算 Key 回诊——诊断仪不存根密钥,防泄露
• 适合 ASIL-D 域控、动力 ECU
2. 离线 DLL(4S/售后场景)
• OEM 给诊断仪厂商发 离线 DLL(Win32/.so),里面封着算法 + 产线批次密钥
• 诊断仪本地算 Key,不联网
• 风险:DLL 被逆向 → 批次密钥泄露 → OEM 只能换批次吊销
• 适合车身 ECU(ASIL-B)、4S 店刷写
五、三个翻车高发点
• Seed 不随机:用 rand() 或固定计数器当 Seed,被重放攻击一次就破。必须用 HSM/RNG(TC3xx 的 HSM 有 TRNG,S32K 有 CSEc RNG),Seed 4 字节起
• 3E 心跳忘了发:编程会话里跑 CRC 要时间(31 RoutineControl CRC 4KB 约 200 ms),超时掉回 Default → 下一帧 $36 直接 NRC 0x33,刷写中断
• App 里没留 27 解锁路径:OTA 场景下 App 收到升级指令后要跳转 Boot,Boot 里 27 必须能过;如果 Boot 的 $27 算法和 App 不一致(版本错位),OTA 卡死 → 车变砖。版本管理要同步
⒡ HSM 加持的 MCU(TC3xx HSM、S32K CSEc、RH850 ICU-S)还能把 $27 的 Key 校验放 HSM 内核跑,主核只拿结果——即使主核代码被攻破,HSM 的根密钥也读不出来,ASIL-D 审计加分项。
车规 MCU Flash 刷写的真谛:10 03→27 01 取 Seed→DLL/KMS 算 Key→27 02 解锁→10 02→34/36/37→31 CRC→11 复位,Seed 必须 HSM RNG 出、Key 算法 OEM 私有、失败计数器 + 延时防暴力、App/Boot 的 27 算法版本同步。这套走完,产线/OTA/4S 三条刷写路径都锁得住,ASIL-D 的"防未授权写入"一条能过。





