结论:Toyota 的 SecOC 链路在源码里是三段彼此独立的机制:密钥装载、同步计数器校验与 panda 安全钩子。它们各自都有明确的边界缺口——密钥只以 16 字节形式留在内存中,Params 的 DONT_LOG 标记并不等于加密;同步 MAC 不匹配时源码只写一条 carlog.error(),不停止组帧;三个消息计数在调用 add_mac() 后、返回候选帧前就递增,不等待 sendcan、SPI、panda TX 或 ECU 的接受回执。发布版对这类车型设 dashcamOnly,MCU 侧也不持有车辆认证密钥。以下为固定快照的静态阅读,未获取车辆密钥、未连接 ECU、未发送 CAN。
能力门与密钥生命周期
Toyota SecOC 平台把认证要求写进 CarParams,并把安全参数传给 panda。ToyotaSecOCPlatformConfig.init() 设置 TSS2/NO_DSU/SECOC 标志和专用 DBC;CarInterface._get_params() 置 secOcRequired 与 Toyota safety 参数,发布版把这类车型设成 dashcamOnly。Car.__init__() 从缓存文件或 Params 读取键,做长度与十六进制解析后交给 CarState/CarController;其中的 secOcKeyAvailable 仅是本地装载标志。
具体地,card 可以从设备本地缓存文件中的 SecOCKey 导入 32 个字符到 Params,再用 bytes.fromhex() 解成 16 字节。正确长度只能证明数据格式,secOcKeyAvailable=True 并不证明它匹配车辆;若 Params 里是非十六进制文本,bytes.fromhex() 在该分支没有局部异常处理,初始化可能直接失败。密钥在 Params 里注册为 DONT_LOG,这只约束常规日志取值,不是存储加密、进程隔离或密钥来源认证。
| 层 | 载体 | 能证明什么 / 不能证明什么 |
|---|---|---|
| 能力门 | CarParams.secOcRequired + dashcamOnly |
声明该车型需要 SecOC;发布版因此进入被动模式,安全配置被改为 NO_OUTPUT。 |
| 密钥装载 | 设备本地缓存文件中的 SecOCKey → Params → 16 字节内存 |
长度与格式可校验;不证明匹配车辆,也不是加密存储。 |
| 日志标记 | SecOCKey 注册为 DONT_LOG |
只约束常规日志取值,不等于进程隔离或来源认证。 |
同步与认证:MAC 不匹配只记日志
SECOC_SYNCHRONIZATION 在 DBC 中是 8 字节消息,含 Trip/Reset 计数和 Authenticator。CarState 每轮复制解析器缓存的当前值,未在此处对同步 MAC 或样本年龄作校验。控制器看到 Reset 与上次不同时,把 LKA、LTA2、ACC2 三个计数清零,再计算期望的同步 MAC。
关键边界在于:不匹配时只有 carlog.error(),没有在该分支停止组帧或撤销控制资格。另有一个初始化细节——首次本地 Reset 初值为零,若同步消息也为零,本轮不会触发这次校验。解析器总线健康属于另一层,控制器在这里没有逐同步帧的年龄门。
签名输出:计数先于链路确认递增
add_mac() 用地址、控制载荷前四字节以及 Trip/Reset/消息计数构造 CMAC 输入,截断后与两组低位标记一起放进 8 字节 CAN 数据;不同命令流各有递增计数。CarController.update() 在 Reset 变化时归零三种本地计数,并分别签 LKA、LTA2、ACC2。
递增时机是核心问题:计数在调用 add_mac() 之后、返回候选 CAN 帧之前增加,不等待 sendcan、SPI、panda TX hook 或 ECU 的接受回执。因此应用层的计数是「本地尝试序号」,不是「车辆已执行序号」;断流、重启、Reset 切换及报文丢失时的同步策略需要另行验证。
| 环节 | 源码可见做法 | 缺口 |
|---|---|---|
| 同步帧解析 | CarState.update() 复制解析器缓存 |
未在此处校验 MAC 或样本年龄。 |
| 同步 MAC 校验 | Reset 变化时清零三计数并算期望值 | 不匹配只 carlog.error(),不停组帧。 |
| Reset 初值 | 首次本地 Reset 为 0 | 同步消息也为 0 时不触发本轮校验。 |
| 命令计数 | add_mac() 后立即递增 |
不等待 sendcan/SPI/panda/ECU 回执。 |
panda safety 与覆盖范围
Toyota safety 的 SecOC TX 白名单包含相应的 LKA/LTA2/ACC2 报文,继续约束消息地址、总线、长度、力矩/角度及纵向输出;例如 SecOC LTA2 的非零执行字段在当前钩子中被禁止。toyota_secoc 参数只有在 ALLOW_DEBUG 编译时才从安全参数开启,不能假定所有固件构建都允许相同模式。
更重要的是责任边界:MCU 侧没有车辆 SecOC 密钥,也不在此代码中校验 AES-CMAC;认证通过与否由车辆 ECU 决定,安全钩子只负责本地发送约束。相邻测试里,通用车型 TX 回放会跳过 SecOC(需要真实键),panda safety 单测会检查 SecOC 地址与限幅,但不验证 ECU 的密码学接受——白名单与限幅通过并不证明车辆会接受签名帧。
自制硬件与插件重构
把车辆认证能力放在受信任、受限的签名服务中,而不是通用车型插件或日志进程:签名服务只对已授权的车型、命令与当前有效同步状态处理候选帧,记录键版本而不泄露键值;CAN 执行仍需独立的 ECU 安全限幅网关。输入契约至少应包含同步帧来源、解析器有效位、接收时间、Trip/Reset 代号和认证结果;MAC 失败、缺帧、过期或密钥不匹配都应进入明确的不可执行状态,不能只写日志。
离线验收要覆盖:未配置键、非法十六进制键、错误键、同步 MAC 不匹配、Reset 为零或跳变、丢帧、重复或过期同步,以及三条命令流计数的重启行为;再在无车台架验证安全钩子与总线发送结果。在没有车辆密钥或 ECU 反馈时,只能确认本地编码与限幅,不应宣称 SecOC 车型已受支持或已可安全上路。
常见问题
SecOCKey 注册为 DONT_LOG 就等于密钥被加密了吗?
不是。DONT_LOG 只约束常规日志取值,它不代表存储加密、进程隔离或密钥来源认证。密钥最终以 16 字节形式留在 CarState/CarController 内存中,正确长度也只证明数据格式,不证明匹配车辆。
同步 MAC 不匹配会阻止发送控制帧吗?
不会。源码在不匹配分支里只有一条 carlog.error(),没有停止组帧或撤销控制资格。此外首次本地 Reset 初值为零,若同步消息也为零,本轮根本不会触发这次校验。
LKA/LTA2/ACC2 的计数是「车辆已执行」的序号吗?
不是。计数在调用 add_mac() 后、返回候选 CAN 帧前就递增,不等待 sendcan、SPI、panda TX hook 或 ECU 回执,因此它只是本地尝试序号,不是车辆已执行的序号。
panda safety 通过就能说明 SecOC 车型可用吗?
不能。panda 只约束消息地址、总线、长度与力矩/角度/纵向限幅;MCU 不持有车辆密钥、也不校验 AES-CMAC。车型回放测试因无密钥会跳过 SecOC TX 用例,白名单与限幅通过并不证明车辆会接受签名帧。
延伸阅读
- Toyota panda safety 模式 —— 承接 SecOC TX 白名单、力矩与角度限幅的固件侧约束。
- panda 故障寄存器与配置写入契约 —— 对照安全模式的编译门与参数配置边界。
- UDS 诊断工具为什么不能随手调:只读、会话与写 ECU 的分权设计 —— 同类「应用层动作不等于 ECU 已接受」的分权思路。
- 从使能到车轮:执行链上的八道门 —— 承接认证、限幅与执行确认之间的独立门。
- Toyota 接口能力与雷达禁用 —— 同一车型能力门与
dashcamOnly语义的对照。
参考资料
- 项目 02 ——
openpilot-research/TOYOTA_SECOC_KEY_FRESHNESS_SAFETY_BOUNDARY_DEEP_DIVE.md(取用了能力门与密钥生命周期、同步与认证边界、add_mac()计数时机、panda safety 覆盖范围与插件重构建议)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11