3、SecOC协议原理:SecOC在CAN/FlexRay上的位置、PDU封装与认证、新鲜度值(Freshness)机制、MAC计算与验证流程

好,咱们进入SecOC的核心部分。说实话,SecOC这个名字听起来挺唬人,但拆开来看,它解决的就是一个很朴素的问题:如何让ECU之间互相确认“你没骗我,消息也是新鲜的”

我在做第一个量产项目时,客户要求所有CAN报文必须带认证。当时我心想,不就是加个校验吗?结果一深入才发现,这里面的门道比想象中多得多。今天我就把SecOC的底裤给你扒干净。

3.1 SecOC在CAN/FlexRay上的位置

先搞清楚SecOC站在哪。在AUTOSAR架构里,SecOC模块位于PDU RouterCanIf/FlexRayIf之间。说白了,它是个中间人。

  • 发送路径:上层(比如COM或DCM)把原始PDU丢给SecOC,SecOC给它“加料”——计算MAC、拼接新鲜度值,然后塞给下层接口发出去。
  • 接收路径:下层接口收到报文后,先交给SecOC。SecOC验MAC、查新鲜度,确认没问题了,才把原始PDU吐给上层。

你想想看,这个位置选得挺巧妙。它不干扰上层应用逻辑,也不改动底层驱动。我习惯把它叫做“透明安全层”——应用工程师甚至不需要知道SecOC的存在,只要配置好就行。

关键点:SecOC不关心你用的是CAN还是FlexRay。它只操作PDU。CAN的8字节限制和FlexRay的254字节限制,那是下层的事。SecOC只管自己的“安全PDU”能不能塞进去。

嗯,这里要注意:CAN的DLC(数据长度码)通常只有8字节,而SecOC要额外塞MAC(4~8字节)和新鲜度值(2~4字节)。所以很多时候,原始PDU的有效载荷会被压缩。我在一个项目中就遇到过,原始信号有6个字节,加上MAC和新鲜度后,死活塞不进8字节的CAN帧。最后只能砍信号,或者用CAN FD。

3.2 PDU封装与认证

SecOC的PDU封装,说白了就是“套娃”。原始PDU进来,SecOC给它包上一层安全外壳。

一个典型的SecOC安全PDU长这样:

+----------------+------------------+------------------+
| 原始PDU (N字节) | 新鲜度值 (M字节)  | MAC (K字节)       |
+----------------+------------------+------------------+

但这里有个坑:顺序不是固定的。AUTOSAR允许你配置新鲜度值放在MAC前面还是后面。我个人习惯把新鲜度值放在MAC前面,因为接收端可以先提取新鲜度值,再算MAC,流程更顺。

认证的核心是MAC。MAC怎么来的?用对称密钥对“原始PDU + 新鲜度值”算一个摘要。注意,不是对整个报文算,而是对安全相关的部分算。我见过有人把整个CAN帧都算进去,结果发现帧ID变了,MAC就失效了——其实帧ID是路由信息,不该参与认证。

我的经验:配置SecOC时,一定要搞清楚哪些信号需要认证,哪些不需要。比如车速信号必须认证,但诊断请求里的某些填充字节可以跳过。否则MAC计算量会大得吓人。

3.3 新鲜度值(Freshness)机制

新鲜度值,英文叫Freshness Value。这玩意儿是防重放攻击的。什么叫重放攻击?就是黑客把之前截获的合法报文,再发一遍。如果没有新鲜度机制,ECU会以为这是新的合法指令。

新鲜度值通常有两种实现方式:

类型 说明 典型长度
时间戳 基于全局同步时间(如GM中定义的GlobalTime) 4字节
计数器 发送端维护一个递增计数器,接收端记录上次收到的值 2~4字节

我个人更倾向于时间戳,尤其是在FlexRay这种时间触发的总线上。为什么?因为时间戳天然是全局唯一的,而且不需要同步状态机。但时间戳有个前提:所有ECU的时间必须同步到微秒级。CAN上通常做不到这么高精度,所以CAN上更多用计数器。

我曾经在一个项目中踩过坑:用计数器做新鲜度,结果接收端和发送端的计数器不同步了——因为ECU重启后计数器归零,而接收端还记着旧值。从那以后,我设计时都会加一个“同步窗口”:允许接收端接受比预期值大一定范围内的计数器值。

警告:新鲜度值不能太长。CAN帧总共才8字节,你塞个8字节的新鲜度,就没地方放MAC和原始数据了。通常新鲜度值控制在2~4字节,MAC控制在4~8字节。剩下的才是有效载荷。

3.4 MAC计算与验证流程

MAC计算,说白了就是“用密钥给数据加个签名”。AUTOSAR SecOC用的是对称密钥,即发送端和接收端共享同一个密钥。算法通常是AES-128-CMAC或AES-128-GMAC。

流程分发送和接收两端:

发送端流程

  1. 从上层拿到原始PDU。
  2. 获取当前新鲜度值(时间戳或计数器)。
  3. 将原始PDU和新鲜度值拼接成一个“认证数据块”。
  4. 用共享密钥对认证数据块计算MAC。
  5. 将原始PDU、新鲜度值、MAC打包成安全PDU,发给下层。

接收端流程

  1. 从下层收到安全PDU。
  2. 拆包:提取原始PDU、新鲜度值、MAC。
  3. 检查新鲜度值是否在可接受范围内(比如时间戳误差不超过100ms,或计数器值大于上次记录值)。
  4. 用同样的共享密钥,对“原始PDU + 新鲜度值”重新计算MAC。
  5. 对比计算出的MAC和收到的MAC。如果一致,认证通过;否则丢弃报文。

你可能会问:为什么MAC计算要包含新鲜度值?直接对原始PDU算不行吗?

嗯,这里有个关键:如果不包含新鲜度值,那MAC只能保证数据没被篡改,但保证不了“这个数据是新的”。黑客完全可以截获一个合法的MAC报文,过一会儿再重放。接收端验MAC能过,但数据是旧的。所以新鲜度值必须参与MAC计算,这样重放的报文MAC就失效了。

避坑指南:我曾经见过一个设计,MAC只算了原始PDU,新鲜度值单独放在报文里。结果黑客把新鲜度值改了,MAC没变,接收端验MAC通过了,但新鲜度值被篡改——等于没防住。记住:新鲜度值必须参与MAC计算,不能单独传输。

最后说一句,MAC计算是有性能开销的。AES-128-CMAC在普通MCU上算一次大概需要几十微秒。如果CAN总线负载率很高,比如每秒上千条报文,那CPU可能就扛不住了。我建议在项目初期就评估好:每条报文都需要SecOC吗?还是只对关键信号做? 这个决策直接影响MCU选型和系统性能。

好了,SecOC的原理就这些。下一章咱们聊聊SecOC的配置和集成,那才是真正动手的地方。