Toyota SecOC 边界缺口:密钥在内存、同步不匹配只记日志、计数先于确认

作者:

在

结论: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 用例,白名单与限幅通过并不证明车辆会接受签名帧。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/TOYOTA_SECOC_KEY_FRESHNESS_SAFETY_BOUNDARY_DEEP_DIVE.md(取用了能力门与密钥生命周期、同步与认证边界、add_mac() 计数时机、panda safety 覆盖范围与插件重构建议)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注