第4章:安全通信基础:SecOC协议概述、MAC计算原理、新鲜度值管理

各位同学,欢迎来到安全通信这一章。说实话,在AUTOSAR的众多模块里,SecOC是我个人觉得最「接地气」的一个。为什么这么说?因为只要你车上有网络通信,就绕不开它。我当年参与的第一个量产项目,就是因为在CAN报文里被人抓包重放,差点出了大事。从那以后,我对SecOC的敬畏心就特别重。

好,咱们今天就把SecOC的底裤扒干净。我会从三个核心点来讲:协议长什么样、MAC怎么算、新鲜度怎么管。你跟着我的思路走,保证不迷路。

4.1 SecOC协议概述:它到底在防谁?

SecOC,全称Secure On-Board Communication。说白了,就是给车载通信加一把锁。你想想看,一辆车上有几十个ECU,大家通过CAN、CAN FD或者以太网互相喊话。如果有个坏蛋在总线上发一条假的刹车指令,后果是什么?

所以SecOC要解决两个核心问题:

  • 消息认证:这条报文是不是来自合法的发送方?
  • 防重放攻击:这条报文是不是被人录下来又放了一遍?

我在项目中遇到过最典型的场景:攻击者用USBCAN设备抓取了一条车窗升降的报文,然后每隔几秒重放一次。结果呢?车主锁车后车窗自己在那上上下下。嗯,这就是没有新鲜度管理的后果。

核心要点:SecOC不是加密报文内容,而是给报文加一个「数字签名」——也就是MAC(消息认证码)。接收方验证MAC通过,才认为这条报文可信。

SecOC在AUTOSAR架构里位于PDU Router和CAN If之间。它拦截所有要发送和接收的PDU,在发送前计算MAC并追加到数据场末尾,在接收后验证MAC并丢弃非法报文。整个过程对上层应用是透明的——应用层根本不知道SecOC的存在,它只管发自己的信号。

4.2 MAC计算原理:一把钥匙开一把锁

MAC的计算,是SecOC最核心的环节。它的公式其实很简单:

MAC = CMAC(密钥, 报文数据 || 新鲜度值)

这里我用的是AES-CMAC算法,这是AUTOSAR官方推荐的。为什么选CMAC而不是HMAC?因为车载ECU的算力有限,CMAC基于AES硬件加速器,算起来快得多。我建议你在选型时优先考虑硬件支持的算法,否则CPU软算MAC会吃掉大量时间。

具体计算过程分三步:

  1. 拼接输入:把原始报文数据和新鲜度值拼在一起,形成一个完整的输入块。
  2. 密钥派生:根据ECU的ID和通信方向(发送/接收),从主密钥派生出会话密钥。注意,每个ECU对之间的密钥都是独立的。
  3. 执行CMAC:用派生密钥对输入块做AES-CMAC计算,得到固定长度的MAC值(通常是4字节或8字节)。

我的经验:MAC长度怎么选?4字节的MAC安全性够用吗?说实话,在CAN 2.0的8字节数据场里,你最多只能塞4字节MAC,否则信号就没地方放了。但在CAN FD里,我建议用8字节。我曾经在项目里为了省带宽用了4字节,结果被安全审计打回来要求整改。嗯,血的教训。

这里有一个容易踩的坑:密钥的存储。密钥绝对不能明文存在Flash里,必须放在HSM(硬件安全模块)中。我曾经见过一个方案,把密钥写死在代码里,结果通过JTAG接口直接读出来了。你说这SecOC还有什么意义?

4.3 新鲜度值管理:让重放攻击无处遁形

新鲜度值(Freshness Value)是SecOC防重放的杀手锏。它的核心思想是:每条报文都带一个「时间戳」或「计数器」,接收方只接受比上次更新的值。

新鲜度值通常由两部分组成:

组成部分 说明 典型长度
时间戳 基于全局时间同步,精度通常为1ms或10ms 32位或64位
计数器 每个发送方维护的递增计数器,溢出后回绕 16位或24位

实际项目中,我更喜欢用「时间戳+计数器」的组合方式。为什么?因为纯时间戳依赖全局时钟同步,万一某个ECU的时钟漂移了,就会误杀合法报文。纯计数器呢?如果ECU重启,计数器归零,接收方会认为所有报文都是旧的。

组合方式的逻辑是这样的:

  • 接收方维护一个「最大有效新鲜度值」的滑动窗口。
  • 收到报文后,先比较时间戳。如果时间戳比窗口内的最大值大,直接接受。
  • 如果时间戳落在窗口内,再比较计数器。计数器必须严格递增。
  • 如果时间戳比窗口最小值还小,直接丢弃。

注意:新鲜度值的同步是个大难题。ECU上电后,需要先通过同步报文获取当前时间戳。如果同步报文本身被篡改了呢?嗯,这就涉及到了安全启动和信任根的问题。我们在后续章节会详细讲。

我记得有一次在台架上调试,发现某个ECU总是丢报文。查了半天,原来是它的时钟晶振精度不够,跑了一天慢了2秒。结果新鲜度值窗口只有1秒,所有合法报文都被当成重放攻击丢弃了。从那以后,我设计窗口大小时都会留出时钟漂移的余量。

4.4 实战中的配置要点

如果你要在AUTOSAR工具链里配置SecOC,有几个参数必须搞清楚:

// SecOC PDU配置示例(伪代码)
SecOCPdu {
  PduId = 0x123;           // 要保护的PDU ID
  MacLength = 4;           // MAC长度,单位字节
  FreshnessLength = 4;     // 新鲜度值长度
  FreshnessType = TIMESTAMP_AND_COUNTER;  // 组合模式
  KeyId = 0x01;            // 密钥索引
  TxMode = TRIGGERED;      // 发送模式:触发式或周期式
}

这里我特别想强调一下发送模式的选择:

  • 触发式(Triggered):信号变化时才发送。优点是省带宽,缺点是接收方需要处理乱序。
  • 周期式(Periodic):不管信号变不变,定时发送。优点是简单,缺点是浪费带宽。

我个人习惯的做法是:关键安全信号(如刹车、转向)用周期式,非安全信号(如车窗、灯光)用触发式。这样既保证了安全性,又不会把CAN总线塞爆。

避坑指南:我曾经在项目里把SecOC的MAC计算放在了中断上下文里。结果呢?AES计算耗时太长,导致中断嵌套,系统直接崩了。记住,MAC计算一定要放在任务级上下文,或者用DMA卸载到硬件加速器。

4.5 小结与思考

好,这一章的内容就到这里。我们讲了SecOC的协议框架、MAC的计算原理、以及新鲜度值的管理策略。你可能会问:这些东西在代码里怎么落地?别急,下一章我会手把手带你写一个SecOC的驱动示例。

最后留一个思考题:如果两个ECU之间的时钟同步完全失效,新鲜度值窗口应该怎么设计才能保证通信不中断?欢迎你在课后自己琢磨一下,我们下节课讨论。