第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会吃掉大量时间。
具体计算过程分三步:
- 拼接输入:把原始报文数据和新鲜度值拼在一起,形成一个完整的输入块。
- 密钥派生:根据ECU的ID和通信方向(发送/接收),从主密钥派生出会话密钥。注意,每个ECU对之间的密钥都是独立的。
- 执行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之间的时钟同步完全失效,新鲜度值窗口应该怎么设计才能保证通信不中断?欢迎你在课后自己琢磨一下,我们下节课讨论。