做 ECU 的都知道,读个版本号、读个故障码无所谓,但刷写程序、写 VIN/序列号、改关键标定这种"危险操作"绝不能谁拿个诊断仪都能干。UDS 里管这道门的,就是0x27 安全访问(Security Access)。
很多项目里 0x27 只是"摆设":seed 写死成固定值、key 算法等于没加密、错了也不锁。本文把它从协议流程到 ECU 落地讲透,让你能设计出真正扛得住的握手。
一、为什么需要它:分级管控
0x27 不是"开/关"一个总闸,而是按安全等级分级。比如:
- 默认会话:只允许读
- 一级解锁(01/02):允许写普通参数
- 二级解锁(03/04):允许进编程会话、刷写
- 三级(05/06):更高权限(如产线终检)
子功能成对出现:奇数请求 seed,偶数提交 key。低等级解锁后高等级服务仍要再解,权限粒度才可控。
二、握手流程(真实报文)
诊断仪 → ECU: 27 01 // 请求一级 seed
ECU → 诊断仪: 67 01 [seed] // 返回随机数 seed(常 4 字节)
诊断仪算 key: key = Algo(seed, Secret)
诊断仪 → ECU: 27 02 [key] // 提交 key
ECU 校验: 67 02 // 通过
或 7F 27 35 // key 无效(NRC=0x35)
seed 和 key 的长度、算法两端必须预先约定一致——算法烧在 ECU 里,等价实现(常是 DLL)发给授权诊断仪。
三、Seed:真随机 + 防重放
最经典的漏洞就是 seed 写死或顺序递增。攻击者抓几次包就能反推 key 算法,甚至直接重放旧 seed/key 登录。
正确做法:
- 用硬件随机数 TRNG生成;没有 TRNG 也要用带时间戳/计数器的 PRNG。
- 连续两次请求不能返回相同 seed(规范没强制,但工程必防重放)。
- 解锁成功后该 seed 立即作废,下次必须重新请求。
四、Key 算法四档(别停在摆设级)
- 最弱:
key = seed + 常数、key = ~seed。一眼被逆,纯走过场。
- 常见:
key = f(seed, 整车厂 Secret),如多轮移位异或、查表、自定义混淆。OEM 把算法烧进 ECU,把 DLL 发给授权工具。
- 较安全:基于 AES-128 等分组密码,seed 作明文/IV、Secret 作密钥,输出作 key;或 HMAC。抗逆向能力强。
- 前沿:非对称 PKI,ECU 持私钥对 challenge 签名,诊断仪用公钥验——适合 EV/ADAS 高安全。
五、必须上"防暴破"
- 失败计数:连错 N 次(常 3 次)锁死,回 NRC
0x36(exceeded attempts),需下电或特殊流程解。
- 时间延迟:连续失败回 NRC
0x37(required time delay not expired),强制诊断仪等待,拖慢暴力。
- 解锁即废 seed:再次访问要重新走一遍握手。
六、ECU 端最小实现骨架
uint8_t seed[4], last[4];
void gen_seed(void)
{
do {
trng_read(seed, 4);
} while (!memcmp(seed, last, 4)); // 防重复
memcpy(last, seed, 4);
}
bool verify_key(uint8_t *key)
{
uint8_t expect[4];
key_algo(seed, SECRET, expect); // 与诊断仪同一套
if (++fail_cnt > 3)
{
locked = true; // 防暴破
}
return !locked && !memcmp(key, expect, 4);
}
七、工程坑
- Secret 别裸奔:算法和密钥放 Bootloader 受保护区,别和 App 一起躺在可读 Flash。
- 刷完要重新上锁:或复位回默认会话,别留高权限。
- 诊断仪 DLL 也要管:OEM 给第三方的算法 DLL 要授权,泄露等于门户大开。
- 长度取舍:4 字节够日常,高安全用 8 字节以上增大暴力成本。
结语
0x27 看着就两个子功能,真要做稳,随机性、算法强度、防暴破、密钥保护每一处都是工程活。设计好了,它是 ECU 的安全闸门;设计烂了,它就是个安慰自己的摆设。