SecurityAccess就是安全访问服务 ,它是一个很重要的诊断服务,涉及到其他众多服务的访问权限设置。
服务 | 描述 |
SecurityAccess | 客户端向服务端请求获取相应的安全访问权限,使客户端有资格去享受其他服务。 |
目录
1、英文术语
英文术语 | 翻译 |
SecurityAccess | 安全访问 |
Seed | 种子 |
Key | 秘钥 |
security concept | 安全概念,也可以称安全算法(种子和密钥的关系) |
SecurityAcces Request SID | 安全访问请求SID ISO14229定义此值为0x27 |
sub-function | 子功能 |
securityAccessType | 安全访问类型 |
requestSeed | 请求种子 |
securityAccessDataRecord | 安全访问数据记录 |
sendKey | 发送密钥 |
SecurityAccess Response SID | 安全访问响应SID ISO14229定义此值为(0x27+0x40) |
securitySeed | 安全种子 |
securityKey | 安全密钥 |
2、缩写表
缩写 | 翻译 |
SID | 服务标识符 |
Cvt | 约定值 M 强制的 C 有条件的 U 用户选项 |
NRC | 否定响应码 |
3、服务描述
3.1.安全访问
安全访问是就是给其他的服务加了一个访问权限,当我们通过安全访问服务去获取这个权限之后,才有资格去享受这些服务。
再打个比喻:某些服务被锁在1号房间里(加一个安全等级1的访问权限),另一些服务被锁在2号房间里(加一个安全等级2的访问权限),...,必须得到相应的钥匙(密钥)打开门进入房间,才能享受里面相应的服务;还有些服务(譬如:比较典型的诊断会话控制服务0x10)在大厅里,大家都不用去获取钥匙,也不用打开锁,就直接可以享受这些在大厅里面的服务。
那这些服务为什么要加安全访问权限呢?
因为某些诊断服务操作会涉及对服务端的功能产生永久性改变,这种服务如果普通人能用诊断仪直接使用这些诊断服务去胡乱操作一通,万一对服务端内部一些功能参数被错误修改,那么整车正常行驶都可能会造成一定危害,严重的甚至造成重大交通事故,比如:
- 写参数服务会通过改变服务端内部的一些标定参数来永久性改变控制器的功能或者性能,如:改写发动机烟度限制参数可以对发动机扭矩响应有一定提升;
- 输入输出控制会操作服务端外部的一些执行器,如果操作不当造成部件损坏或人身危险;
如何使用安全访问服务?
- 客户端请求“种子”
- 服务端发送“种子”
- 客户端发送“密钥”
注:客户端根据与服务端事先商定好的一套安全算法,把服务端发来的种子作为输入,生成密钥。 - 服务端响应“密钥”有效时,服务端会自行解锁。
注意:
- 在任何时刻,都只能有一个安全级别处于活动状态。
譬如:当没有使用安全访问服务时,没有去解锁,这时候我们默认的安全解锁状态为
static SecurityLevel m_SecurityLevel = LEVEL_ZERO; //当前安全解锁等级,当要使用的服务不涉及安全访问时,使用我们安全解锁状态就设置为LEVEL_ZERO,此时我们在这种安全解锁状态下就可以直接访问没有设置权限的诊断服务。typedef enum{ LEVEL_ZERO = 7, //安全等级0,当一个服务不需要安全解锁时,使用此安全等级 LEVEL_ONE = 1, //安全等级1,当一个服务可在安全等级1时,使用此安全等级-解锁之后就进入此等级了 LEVEL_TWO = 2, //安全等级2,当一个服务可以在安全等级2时,使用此安全等级 LEVEL_THREE = 4,//安全等级3,当一个服务可以在安全等级3时,使用此安全等级 LEVEL_FOUR = 8, //安全等级4,工厂模式会话使用此安全等级,用户零部件商下线配置(自定义的) LEVEL_UNSUPPORT = 0, //不支持,当一个服务在某个会话模式不支持时,使用此等级 }SecurityLevel;//安全解锁等级 static SecurityLevel m_SecurityLevel = LEVEL_ZERO; //当前安全解锁等级
- 上面安全等级的编号说任意的,并不是表示这些级别有某种关系,如:LEVEL_ONE安全等级1和LEVEL_TWO安全等级2就没有任何联系,没有高低之分。
- 不同安全等级可以设置不同的安全算法与之对应,也可以直接统一使用一种安全算法。
- “请求种子”子功能参数值应始终为奇数,并且同一安全等级对应的“发送密钥”子功能参数值应等于“请求种子”子功能参数值加上1。
如:客户端想要向服务端获取安全等级1的权限,客户端先向服务端请求种子(子功能参数0x01,此子功能参数是奇数),服务端就会向客户端发送种子,客户端根据种子和安全算法得到密钥(其实服务端在向客户端发送种子时,已经将密钥计算出来,并记录在软件内部),当客户端向服务端发送算出的密钥(子功能参数为0x01+1=0x02)时,这时,服务端将记录在软件内部的密钥跟客户端发来的密钥进行对比,如果密钥匹配,服务端自行解锁并发出肯定响应;如果密钥不匹配,服务端不会进行解锁,并发出否定响应,服务端认为此次访问错误访问尝试,这样客户端前面请求种子就不作数了,如果还想解锁,客户端必须要从重新请求种子。- 安全访问服务需要在非默认会话模式下进行的,所以我们在使用安全访问服务之前,需要使用诊断会话控制服务。
- 如果服务端支持安全访问,当服务端已经解锁(譬如:服务端已经处于LEVEL_ONE状态),再去接收到客户端发来的对应的安全访问"请求种子"消息(譬如:对应的"请求种子 = 0x01"),服务端将响应的种子值赋值为0。如果服务端重新被锁住(譬如:服务端已经回到LEVEL_ZERO状态),这时候发送的种子值就不能是全零的了,而是从服务端内部通过算法随机生成一个随机种子。
注意:客户端可以利用此方法来识别服务端是不是已经被锁住,之后再去考虑下一步是可以直接去请求涉及安全访问的服务,还是继续进行解锁操作。- 如果服务端已经被锁住,再去请求涉及安全访问的服务,服务端就可以给出否定响应(涉及到安全访问的服务都有与对应的否定响应码)。
3.2.延时计时器
延迟计时器是指当错误访问尝试超过允许最大次数时,ECU自己会利用该计时器定一段时间内(譬如:定15s)不允许再次响应请求种子指令,这样就可以避免一直尝试破解,当该延迟计时器达到设定的时间后错误访问尝试次数减1(延时计时器并恢复到默认状态并停止计时),允许一次尝试,如果尝试成功,错误访问尝试次数清零,延迟计时器恢复到默认状态并停止计时;如果尝试失败(这时候又达到了一次尝试访问超限),再次启用延迟计时器。
注意:
1、错误访问尝试次数需要存储在非易失性存储器(如:EEPROM)中,以保证掉电不丢失,以防有些其他人员通过下电的方式去重复尝试破解。如果上次上电已经到达了错误尝试的上限值,当再次上电的时候,延时计时器会被重新激活,跟上面的过程是一致的。
2、就算是上次上电错误访问尝试次数没有达到上线值,这次服务端上电/复位后,有一些车辆制造商也会去定义一个延时(这个延时可以用上面的延迟计时器来计时,也可以另外定义,时间长短也可以定义和上面访问超限定义的延迟时间不一样)去对安全访问服务使用进行保护。
你可以把上面的过程对比成手机的解锁过程就能理解。
ISO14229明确指出车辆制造商可以选择是否支持延迟计时器。
4.请求消息定义
请求消息定义子功能 = 请求种子(奇数)
请求消息定义子功能 = 请求种子 | ||||
A_Data字节 | 参数名称 | Cvt | 字节值 | 助记符 |
#1 | 安全访问请求SID | M | 0x27 | SA |
#2 | 子功能 = [安全访问类型 = 请求种子] | M | 0x01,0x03, 0x05, 0x07-0x7D |
LEV_ SAT_RSD |
#3 : #n |
安全访问数据记录 = [ 参数#1 : 参数#(n-2)] |
U : U |
0x00 - 0xFF : 0x00 - 0xFF |
SECACCDR_ PARA1 : PARAn-2 |
请求消息定义子功能 = 发送密钥(偶数)
请求消息定义子功能 = 发送密钥 | ||||
A_Data字节 | 参数名称 | Cvt | 字节值 | 助记符 |
#1 | 安全访问请求SID | M | 0x27 | SA |
#2 | 子功能 = [安全访问类型 = 发送密钥] | M | 0x02,0x04, 0x06, 0x08-0x7E |
LEV_SAT_SK |
#3 : #n |
安全密钥 = [ 密钥#1(高字节) : 密钥#(n-2)(低字节)] |
M : U |
0x00 - 0xFF : 0x00 - 0xFF |
SECKEY_ KEY1HB : KEY(n-2)LB |
注:可以看到安全密钥中密钥#1(高字节),它的约定值是强制的,所以密钥第一个字节必须要有,整个安全密钥由多少个字节是由车辆制造商共同定义的,并不是固定由多少个字节组成。
4.1.请求消息子功能定义
该服务使用子功能参数选择(此字节的bit7表示抑制肯定响应位,下表并没有介绍此位)。
如果服务端支持不同的安全等级,则每个等级应由请求种子值来认定,该请求种子值与发送密钥值具有固定关系:
- "请求种子 = 0x01"确定了"请求种子 = 0x01"和"发送密钥 = 0x02"之间的固定关系;
- "请求种子 = 0x03"确定了"请求种子 = 0x03"和"发送密钥 = 0x04"之间的固定关系;
- "请求种子 = 0x05"确定了"请求种子 = 0x05"和"发送密钥 = 0x06"之间的固定关系;
- "请求种子 = 0x07"确定了"请求种子 = 0x07"和"发送密钥 = 0x08"之间的固定关系;
- "请求种子 = 0x41"确定了"请求种子 = 0x41"和"发送密钥 = 0x42"之间的固定关系;
请求消息子功能定义 | |||
位6-0 | 描述 | Cvt | 助记符 |
0x00 | ISOSAEReserved 该值为本文件为未来定义预留。 |
M | ISOSAERESRVD |
0x01 | 请求种子 由车辆制造商定义的具有安全等级的请求种子。 |
U | RSD |
0x02 | 发送密钥 由车辆制造商定义的具有安全等级的发送密钥。 |
U | SK |
0x03,0x05, 0x07 - 0x41 |
请求种子 由车辆制造商定义的具有不同安全等级的请求种子 |
U | RSD |
0x04,0x06, 0x08 - 0x42 (都是偶数) |
发送密钥 由车辆制造商定义的具有不同安全等级的发送密钥 |
U | SK |
0x43 - 0x5E |
ISOSAEReserved 该值为本文件为未来定义预留。 |
M | ISOSAERESRVD |
0x5F |
ISO26021-2请求种子 对具有不同安全等级的请求种子的定义是为了激活ISO26021-2中定义的报废期的车载打火装置。 |
U | RSD |
0x60 | ISO26021-2发送密钥 对具有不同安全等级的发送密钥的定义是为了激活ISO26021-2中定义的报废期的车载打火装置。 |
U | SK |
0x61 - 0x7E | 系统供应商特定 此值范围保留以供系统供应商特定使用。 |
U | SSS |
0x7F | ISOSAEReserved 该值为本文件为未来定义预留。 |
M | ISOSAERESRVD |
4.2.请求消息数据参数定义
请求消息数据参数定义 |
定义 |
安全密钥 请求消息中的密钥是利用对应的种子值向安全算法中输入生成的值。 |
安全访问数据记录 这个参数记录是车辆制造商可以自行设置的,用于在请求种子信息时将数据传输到服务端。也可加可不加,我们也可利用这个参数做身份识别,对客户端进行校验。 |
5.肯定响应消息定义
对应请求种子的肯定响应消息定义
对应请求种子的肯定响应消息定义 | ||||
A_Data字节 | 参数名称 | Cvt | 字节值 | 助记符 |
#1 | 安全访问响应SID | M | 0x67 | SAPR |
#2 | 子功能 = [安全访问类型 = 请求种子] | M | 0x00 - 0x7F中的奇数 | LEV_SAT_SK |
#3 : #n |
安全种子[] = [ 种子#1(高字节) : 种子#(n-2)(低字节)] |
M U |
0x00 - 0xFF : 0x00 - 0xFF |
SECSEED_ SEED1HB : SEED(n-2)HB |
对应发送密钥的肯定响应消息定义
对应发送密钥的肯定响应消息定义 | ||||
A_Data字节 | 参数名称 | Cvt | 字节值 | 助记符 |
#1 | 安全访问响应SID | M | 0x67 | SAPR |
#2 | 子功能 = [安全访问类型 = 发送密钥] | M | 0x00 - 0x7F中的偶数 | LEV_SAT_SK |
5.1.肯定响应消息数据参数定义
肯定响应消息数据参数定义 |
定义 |
安全访问类型 此参数与请求消息的子功能参数的位6-0相同。 |
安全种子 安全种子是服务端发送的数据值,客户端在计算密钥时需要使用该值。 |
6.否定响应消息定义
否定响应消息定义 | ||||
A_Data字节 | 参数名称 | 字节值 | Cvt | 助记符 |
#1 | 否定响应SID | 0x7F | M | SIDNR |
#2 | 安全访问请求SID | 0x27 | M | SIDRQ |
#3 | 否定响应码 | 0xXX | M | NRC_ |
6.1.否定响应码
此服务支持的否定响应码 | ||
否定响应码 | 定义 | 助记符 |
0x12 | 子功能不支持 如果不支持子功能参数,则应发送此否定响应码。 |
SFNS |
0x13 | 消息长度错误或格式无效 如果消息的长度错误,则应发送此否定响应码。 |
IMLOIF |
0x22 | 条件不正确 如果不满足安全访问请求的条件,则应发送此否定响应码。 |
CNC |
0x24 | 请求顺序错误 如果在没有收到“请求种子”请求消息的前提下,接收到“发送密钥”,则发送此否定响应码。 |
RSE |
0x31 | 请求超出范围 如果用户可选的安全访问数据记录包含无效数据,则发送此否定响应码。 |
ROOR |
0x35 |
无效密钥 如果接收到发送密钥,并且该密钥的值与服务端的内部存储/计算密钥不匹配,则发送此否定响应码。 |
IK |
0x36 | 尝试次数超限 如果延迟计时器因错误访问尝试超过允许最大次数(我们可以设置一个密钥错误计数器,并设置一个错误上限次数,当达到这个上限次数时,ECU自己会定一段时间内(譬如:定15s)不允许再次响应请求种子指令(这时如果再去请求种子,ECU就会发送此否定响应码的否定响应),这个定一段时间就是利用这个延迟计时器来控制的,这样就可以避免一直尝试破解,当该延迟计时器达到设定的时间后错误访问尝试次数减1,允许一次尝试)而处于活动状态,则发送此否定响应码。 简单点说: 假设服务端设的错误访问次数最大限值是3,你的前两次到了发送密钥环节因发送的密钥不匹配而出现的错误,则服务端会返回NRC=0x35,第三次密钥错误才返回NRC=0x36,这时延时计时器就会被激活。 |
ENOA |
0x37 | 延时时间未到 还在上面定的时间内,禁止再次响应请求种子,也就是上面延迟计时器还在计时,则发送此否定响应码。 |
RTDNE |
7.服务使用示例
7.1.示例#1-服务端进入LEVEL_ONE安全等级状态
服务端处于"锁定"状态(未解锁状态)且为非默认会话模式下,如果想进入LEVEL_ONE安全等级状态下,则必须满足以下条件才能成功解锁服务端,让服务端处于LEVEL_ONE安全等级状态下:
- 请求种子的子功能:0x01(请求种子)
- 发送密钥的子功能:0x02(发送密钥)
- 服务端的随机种子(2个字节):0x3657(例子)
- 服务端的密钥(2个字节):0xC9A9(例如:安全算法:种子值的二补数)
客户端通过将抑制肯定响应位(子功能参数的位7)设置为“False”向服务端请求。
7.1.1.步骤1:请求种子
请求消息子功能 = 请求种子 | ||||
消息方向 |
客户端→服务端 |
|||
消息类型 |
请求 |
|||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问请求SID |
0x27 |
SA |
|
#2 |
安全访问类型= 请求种子, 抑制肯定响应位 = 假 |
0x01 |
SAT_RSD |
对应请求种子的肯定响应消息 | ||||
消息方向 |
服务端→客户端 |
|||
消息类型 |
响应 | |||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问响应SID |
0x67 |
SAPR |
|
#2 |
安全访问类型= 请求种子 |
0x01 |
SAT_RSD | |
#3 #4 |
安全种子[字节#1] = 种子#1(高字节) 安全种子[字节#2] = 种子#2(低字节) |
0x36 0x57 |
SECHB SECLB |
7.1.2.步骤2:发送密钥
请求消息子功能 = 发送密钥 | ||||
消息方向 |
客户端→服务端 |
|||
消息类型 |
请求 |
|||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问请求SID |
0x27 |
SA |
|
#2 |
安全访问类型= 发送密钥, 抑制肯定响应位 = 假 |
0x02 |
SAT_SK | |
#3 #4 |
安全密钥[字节#1] = 密钥#1(高字节) 安全密钥[字节#2] = 密钥#2(低字节) |
0xC9 0xA9 |
SECKEY_HB SECKEY_LB |
对应发送密钥的肯定响应消息 | ||||
消息方向 |
服务端→客户端 |
|||
消息类型 |
响应 |
|||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问响应SID |
0x67 |
SAPR |
|
#2 |
安全访问类型= 发送密钥 |
0x02 |
SAT_SK |
7.1.3.操作汇总
7.2.示例#2-服务端处于LEVEL_ONE安全等级状态
处于此安全等级状态下,再去请求种子(与此安全等级状态对应的种子)。
7.2.1.步骤1:请求种子
请求消息子功能 = 请求种子 | ||||
消息方向 |
客户端→服务端 |
|||
消息类型 |
请求 |
|||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问请求SID |
0x27 |
SA |
|
#2 |
安全访问类型= 请求种子, 抑制肯定响应位 = 假 |
0x01 |
SAT_RSD |
对应请求种子的肯定响应消息 | ||||
消息方向 |
服务端→客户端 |
|||
消息类型 |
响应 |
|||
A_Data字节 |
描述(所以值为16进制) |
字节值 |
助记符 |
|
#1 |
安全访问响应SID |
0x67 |
SAPR |
|
#2 |
安全访问类型= 请求种子 |
0x01 |
SAT_RSD | |
#3 #4 |
安全种子[字节#1] = 种子#1(高字节) 安全种子[字节#2] = 种子#2(低字节) |
0x00 0x00 |
SECHB SECLB |
注:这时客户端可以根据安全种子值都为0,判断服务端是否为锁定状态。