利用影子认证帧在标准CAN上实现防重放的技术方案

一个几乎百搭、不改架构、低成本就能落地的整车防重放方案,不是什么颠覆性创新,但或许能给大家带来一点启发:

把原来单独的业务指令(业务帧)变成一个 “指令+动态密码” 的组合,这个 “动态密码” 被打包在一个单独的帧里,这里称为认证帧。因为认证帧和指令帧绑定且紧随其后,就像影子一样,所以这里就叫它 “影子认证帧”

也就是说,要实现该方案只需要给每一个关键控制帧后面额外增加一帧即可。如果觉得单纯计数器不够安全、本身没有用CANFD、想低成本短时间满足要求的可以参考本文。下面,给大家解析这个方案该如何开展

确定保护范围

因为方案是基于“庞大的整车电子电气架构牵一发而动全身,改造起来成本高昂、协同困难或没有多余的数据场位做校验”的原因考虑,所以在开始实施的阶段,我们只需要筛选出一些需要防重放的报文,而不是所有:

  • 识别网络中所有需要防重放保护的关键控制功能(可以根据TARA也可以自己凭经验列举)
  • 列出这些关键帧的CAN ID清单

影子认证帧ID分配规则

筛选出清单后,需要为清单中的每一个关键帧CAN ID (x) 分配一个唯一的影子认证帧CAN ID (x')

分配规则x' = x + Base_Numb(例如,Base_Numb = 0x100),或根据预定义的映射表进行分配

前置条件:必须确保所有分配的 x' 不与网络中任何现有帧ID发生冲突,需要查询整车CAN矩阵进行确认

示例:需要做防重放的关键帧ID为0x100,那么根据前面分配规则说到的公式0x100 + 0x100 = 0x200,分配的影子认证帧ID就为0x200

影子认证帧数据结构

参照标准CAN格式,统一影子认证帧的数据场格式为8字节,具体定义如下:

字节索引 字段名称 长度 值/描述
0 计数器
(ctr)
1 Byte 单调递增的计数值,范围0x00~0xFF(0-255),发送后递增
1 认证标签高字节
(Tag_H)
1 Byte 由CMAC算法生成的16位认证标签的高位字节
2 认证标签低字节
(Tag_L)
1 Byte 由CMAC算法生成的16位认证标签的低位字节
3-7 时间戳/状态
(Time_Stamp)
N Byte 用于同步或增强防回滚能力的时间片或状态字,剩余可填充0xAA

算法和密钥管理

  • 为了方便管理,统一加密算法为AES128-CMAC(或其他算法)
  • 为所有关键帧ID(x)分配一个统一的加密密钥(key) //实际上应该是为每一个关键帧ID单独分配密钥更安全,但考虑到大多数不想这么麻烦,所以可以改成统一密钥
  • 通过安全刷写将密钥 (key) 写入发送ECU和接收ECU的安全存储中,如果没有条件存储,可以将密钥编码在固件中

技术实现

发送端ECU实现

发送端需要在原有发送流程中集成以下步骤:

1.当应用层请求发送受保护的关键帧(s)时,获取当前计数器值(ctr)以及当前时间片 Time_Stamp

2.构造CMAC计算所需的输入数据:Input = s.ID (2B) || s.DATA (8B) || ctr (1B) || Time_Stamp (1B) //此处时间戳假设为1个字节,如果不够,根据情况扩充

3.使用密钥(key)计算CMAC:Full_Tag = CMAC(key, Input)

4.截取Full_Tag的前16个比特位作为本次发送的认证标签(Tag16) //2个字节,填充高低位

5.先发送业务帧(s)

6.然后发送影子认证帧(x'),数据场按数据结构的定义格式填充:

1
[ctr, Tag_H, Tag_L, Time_Stamp, 0xAA, 0xAA, 0xAA, 0xAA]

7.发送成功后,更新计数器(ctr)

逻辑流程图如下

1
2
3
4
5
6
7
8
graph TD
A[开始发送] --> B[获取ctr和Time_Stamp]
B --> C[构造CMAC输入数据]
C --> D[计算并截取Tag16]
D --> E[发送业务帧s]
E --> F[发送影子认证帧x']
F --> G[更新计数器]
G --> H[结束]

image

接收端ECU实现

接收端需要实现一个新的验证状态机制:

1.当收到业务帧(s)时,将其数据副本与接收时间戳存入缓存池,并暂停该帧的应用层处理

2.启动一个定时器(根据业务和网络负载调整),等待对应的影子认证帧(x')

3.超时处理:若超时仍未收到(x'),则从缓存中丢弃该业务帧(s),不再处理

4.若收到影子认证帧(x'):

a. 从缓存中查找与之匹配的业务帧(s)

b. 从(x')数据场中提取 ctr_recvTag16_recv以及Time_Stamp_recv

c. 使用密钥 (key)、缓存的s.IDs.DATA 以及提取的 ctr_recvTime_Stamp_recv,按照发送流程重新计算期望的认证标签(Tag16_calc)

d. 计算输入:Input_calc = s.ID (2B) || s.DATA (8B) || ctr_recv (1B) || Time_Stamp_recv (1B)

e. 计算:Tag16_calc = Truncate(CMAC(key, Input_calc), 16) //截断取前两个字节用作高低位

f. 验证对比:

  • Tag16_calc != Tag16_recv,验证失败,丢弃业务帧(s),不再处理
  • Tag16_calc == Tag16_recv,进行Time_Stamp有效性校验和计数器防重放检查

5.清除缓存中对应的业务帧(s)

6.更新计数器

逻辑流程图如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
graph TD
A[收到业务帧s] --> B{帧s.ID是否在<br>受保护列表?}
B -- 否 --> C[提交应用层处理]
B -- 是 --> D[缓存s.ID与s.DATA<br>启动超时定时器]

D --> E{在超时时间内<br>收到影子认证帧x'?}
E -- 否 --> F[丢弃缓存帧s<br>(可选项):触发超时告警]

E -- 是 --> G[提取ctr_recv, Tag16_recv, Time_Stamp_recv]
G --> H[根据缓存数据计算<br>Tag16_calc]
H --> I{Tag16_calc ==<br>Tag16_recv?}
I -- 否 --> J[认证失败<br>丢弃帧s]

I -- 是 --> K{Time_Stamp_recv有效?<br>(等于或接近My_Time_Stamp)}
K -- 否 --> L[时间片无效→重放攻击<br>丢弃帧s]

K -- 是 --> M{ctr_recv有效?<br>(在预期狭小窗口内)}
M -- 否 --> N[计数器无效→重放攻击<br>丢弃帧s]

M -- 是 --> O[验证成功]
O --> P[提交业务帧s至应用层]
O --> Q[更新last_valid_ctr = ctr_recv]

J --> R[结束]
L --> R
N --> R
P --> R
Q --> R

subgraph 接收端统一清理
R[清除缓存中本帧记录]
end

image

示例

发送端:

场景: 100

1.业务帧(s):

1
2
s.ID = 0x100
s.DATA = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

2.获取静态配置:

  • 密钥 key = 0xsecretkey
  • 影子认证帧ID x' = 0x200
  • 当前计数器 ctr = 0x0B
  • 当前时间片 Time_Stamp = 0x03

3.执行流程:

a. 拼接 Input: 0x100 + [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + 0x0B + 0x03

b.key 计算 Input CMAC,得到 Tag16

c. 发送第一帧(业务帧):

1
ID=0x100, Data=[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

d. 发送第二帧(认证帧):

1
ID=0x200, Data=[0x0B, Tag_H, Tag_L, 0x03, 0xAA, 0xAA, 0xAA, 0xAA]

e.0x100 对应的计数器加一,变为 0x0C

发送端示例流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
flowchart TD
A[应用层请求发送关键帧s] --> B[获取ctr和Time_Stamp]
B --> C[构造输入数据: Input = s.ID + s.DATA + ctr + Time_Stamp]
C --> D[计算Full_Tag = CMACkey, Input]
D --> E[截取Tag16 = 前16位]
E --> F[发送业务帧s]
F --> G[发送影子认证帧x'<br>数据: ctr, Tag_H, Tag_L, Time_Stamp, 填充]
G --> H[更新计数器: ctr = ctr + 1]
H --> I[发送完成]

subgraph 示例配置
J[帧ID: 0x100]
K[数据: 0100000000000000]
L[计数器: 0x0B]
M[时间片: 0x03]
N[影子认证帧ID: 0x200]
O[密钥: 0xsecretkey]
end

image

接收端:

接收端自身维护一个当前的有效时间片值My_Time_Stamp(比如由网关同步或根据自身系统时间计算得出)和为每个发送源(或每个帧ID)维护一个最后有效的计数器值(last_valid_ctr)

校验流程:

1.总线收到一帧:ID=0x100, Data=[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

a.接收端逻辑:这是受保护的关键帧s,我需要等待它的影子认证帧进行验证

b.动作:将这份数据s.IDs.DATA 存入缓存池,并暂停对该帧的应用层处理,启动一个定时器(例如200ms)

2.总线收到下一帧:ID=0x200, Data=[0x0B, Tag_H, Tag_L, 0x03, 0xAA, 0xAA, 0xAA, 0xAA]

a.接收端逻辑:这是我正在等待对应 0x100 的影子认证帧

b.动作:

1)从缓存中查找影子认证帧对应的ID=0x100业务帧数据
2)从影子认证帧数据场中提取:

1
2
3
* ctr_recv = 0x0B
* Tag16_recv = 0x0102 (高字节0x01 + 低字节0x02) //Tag_H、Tag_L,此处假设是0102
* Time_Stamp_recv = 0x03

3.接收端开始重现发送端的计算过程:

a. Input_calc = s.ID (2B) || s.DATA (8B) || ctr_recv (1B) || Time_Stamp_recv (1B) //拼接输入
b. Full_Tag_calc = CMAC(key, Input_calc) //计算CMAC

c. Tag16_calc = Truncate(Full_Tag_calc, 16) //取前两个字节

4.比较 Tag16_calc (0x0102)Tag16_recv (0x0102),通过则下一步,不通过丢弃

5.检查时间片:比较Time_Stamp_recv和接收端自己的My_Time_Stamp,通过则下一步,不通过丢弃

6.检查计数器:接收端预期下一个计数器是 0x0B,收到的ctr_recv0x0B,通过则执行动作,不通过丢弃

7.更新 last_valid_ctr = ctr_recv

接收示例流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
flowchart TD
A[收到业务帧s] --> B{受保护帧?}
B -- 否 --> C[提交应用层]
B -- 是 --> D[缓存数据并启动定时器]
D --> E{收到影子认证帧?}
E -- 超时 --> F[丢弃帧s]
E -- 收到 --> G[提取ctr_recv, Tag16_recv, Time_Stamp_recv]
G --> H[计算Tag16_calc]
H --> I{标签验证?}
I -- 失败 --> J[认证失败→丢弃]
I -- 成功 --> K{时间片有效?}
K -- 无效 --> L[时间片无效→丢弃]
K -- 有效 --> M{计数器有效?}
M -- 无效 --> N[计数器无效→丢弃]
M -- 有效 --> O[验证成功]
O --> P[提交应用层]
O --> Q[更新last_valid_ctr]
F --> R[清理缓存]
J --> R
L --> R
N --> R
P --> R
Q --> R
subgraph 示例数据
S[业务帧: ID=0x100, Data=0100000000000000]
T[影子认证帧: ID=0x200, Data=0B010203AAAAAAAA]
U[提取: ctr=0x0B, Time_Stamp=0x03]
end

image

总结

本方案结合了之前的项目,有成功落地的案例,并在此基础上做了优化,后面可以根据自身情况更改、删减一些做不到的或根据自身条件增加更多的验证

最后,别感冒
image




声明:
本文章用于学习交流,严禁用于非法操作,出现后果一切自行承担,阅读此文章表示你已同意本声明。

Disclaimer:
This article is for study and communication. It is strictly forbidden to use it for illegal operations. All consequences shall be borne by yourself. Reading this article means that you have agreed to this statement.