系列文章目录
使用CAPL脚本解析hex、S19、vbf文件
使用CAPL脚本对CAN报文的Counter、CRC、周期、错误帧进行实时监控
使用CAPL脚本模拟发送符合协议要求(Counter和CRC)的CAN报文
使用CAPL脚本控制继电器实现CAN线、电源线的通断
使用CAPL脚本实现安全访问解锁
使用CAPL脚本实现BUS OFF干扰测试
使用CAPL脚本进行DTC自动化测试
使用CAPL脚本进行UDS刷写及其自动化测试
使用CAPL脚本进行UDS协议测试
使用CAPL脚本进行网络管理测试
粉丝问题解答系列文章… …
其他持续更新中… …
文章目录
- 系列文章目录
- 前言
- 一、利用安全算法.dll文件解锁
- 1、函数介绍
- 2、示例代码
- 二、利用自定义安全算法解锁
- 1、示例代码
- 三、测试场景示例
- 1、正常的安全访问解锁
- 2、安全访问秘钥错误
- 3、连续多次申请解锁,验证种子随机性
- 总结
前言
在车载诊断中,我们经常会使用到安全访问解锁,常见的场景包括软件刷写前、通过2E服务写入某个DID前、通过31服务执行某个例程前等,都必须先通过27服务进行安全访问,解锁相应安全等级后才能执行上述操作。
在开发调试或测试时,有时候需要模拟正常的安全解锁、安全访问秘钥错误、连续多次请求安全访问等,手动操作极为不便。本文将介绍如何使用CAPL脚本实现安全访问解锁,并模拟各种测试场景。
一、利用安全算法.dll文件解锁
1、函数介绍
diagGenerateKeyFromSeed函数适用于利用CANoe工程中配置的安全算法动态链接库文件(.dll)生成秘钥的场景。详细说明如下:
2、示例代码
利用CANoe工程中配置的安全算法动态链接库文件(.dll)进行安全访问解锁。CAPL脚本示例如下:
variables
{
byte glob_SeedArray[10];
}
//发送报文,并获取响应状态
long diagSendReqAndgetResp(diagRequest * req)
{
long NRC;
diagSendRequest(req);
testWaitForDiagRequestSent(req, 200);
if(1 == TestWaitForDiagResponse(req,5000))
{
NRC = diagGetLastResponseCode(req);
if(NRC == -1)//积极响应
{
return 1;
}
else//消极响应
{
TestReportWriteDiagResponse(req);
return NRC;
}
}
testStep("step","响应超时");
return -1;//响应超时
}
//安全访问解锁函数:level——解锁的安全等级;errorMode——是否要模拟密钥错误;SeedArray_only——是否只申请种子,不进行解锁
long diagUnlockECU(byte level,byte errorMode,byte SeedArray_only)
{
long Resp;
byte gSeedArray[20];
int gSeedArraySize,gSecurityLevel;
char gVariant[50] = "Base_Variant";
char gOption[50] = "option";
byte gKeyArray[20];
int gMaxKeyArraySize = 255;
dword gActualSizeOut;
long errorcode,size;
diagRequest * req;
diagResponse * resp;
byte reqBuffer[20];
req.Resize(2);
diagSetPrimitiveByte(req,0,0x27);
diagSetPrimitiveByte(req,1,level);
gSecurityLevel = level;
testStep("","安全访问申请Seed");
/*请求Seed*/
Resp = diagSendReqAndgetResp(req);
if( Resp == 1 )
{
/*将响应Seed写入参数数组*/
diagGetLastResponse(req, resp);
/*获取Seed&Key的长度*/
size = resp.GetPrimitiveSize();
gSeedArraySize = size - 2;
gActualSizeOut = size - 2;
write("Seed&Key size:%d",size);
resp.GetPrimitiveData(gSeedArray, elcount(gSeedArray));
memcpy_off( gSeedArray,0,gSeedArray,2, gSeedArraySize);
if( SeedArray_only == 1 )
{
memcpy( glob_SeedArray,gSeedArray, gSeedArraySize);//仅申请安全访问种子
return 0xFF;
}
/*计算密钥(使用安全算法动态链接库文件(.dll)生成密钥)*/
errorcode = diagGenerateKeyFromSeed(gSeedArray, gSeedArraySize, gSecurityLevel, gVariant, gOption, gKeyArray, gMaxKeyArraySize, gActualSizeOut);
if( 0 == errorcode)
{
/*写入请求参数*/
req.Resize(gActualSizeOut + 2);
reqBuffer[0] = 0x27;
reqBuffer[1] = level+1;
memcpy_off(reqBuffer,2,gKeyArray,0,gActualSizeOut);
if(errorMode == 1)
{
reqBuffer[4] += 1;//模拟制造密钥错误
}
req.SetPrimitiveData(reqBuffer,size);
/*发送密钥*/
testStep("","发送密钥进行解锁");
Resp = diagSendReqAndgetResp(req);
return Resp;
}
else
{
TestStepFail("","密钥计算出错,errorcode:%x",errorcode);
return errorcode;
}
}
else
{
return Resp;
}
}
二、利用自定义安全算法解锁
前面说了diagGenerateKeyFromSeed函数是利用CANoe工程中配置的安全算法动态链接库文件(.dll)生成的秘钥,使用起来很方便,不需要再编写生成生成密钥的算法函数。但是有些测试场景可能不是很方便,比如利用自动化脚本分别进入扩展会话和编程会话进行解锁,一般来说扩展会话和编程会话模式下的密钥算法是不一样的,不能通过同一个动态链接库文件(.dll)生成秘钥。这个时候可以自定义一个密钥生成函数来替换diagGenerateKeyFromSeed函数即可。
1、示例代码
利用自定义的安全算法函数进行安全访问解锁。CAPL脚本示例如下:
variables
{
byte glob_SeedArray[10];
}
//发送报文,并获取响应状态
long diagSendReqAndgetResp(diagRequest * req)
{
long NRC;
diagSendRequest(req);
testWaitForDiagRequestSent(req, 200);
if(1 == TestWaitForDiagResponse(req,5000))
{
NRC = diagGetLastResponseCode(req);
if(NRC == -1)//积极响应
{
return 1;
}
else//消极响应
{
TestReportWriteDiagResponse(req);
return NRC;
}
}
testStep("step","响应超时");
return -1;//响应超时
}
//根据种子计算密钥(示例)
dword SeedToKey1(byte pData[],byte level)
{
dword seed = 0;
dword ret = 0;
dword PositionA,PositionB,PositionC;
dword B24 = 0;
int i;
qword ChallengeBitArray;
ret = 0;
PositionA = 0x123456;//示例
PositionB = 0;
PositionC = 0;
B24 = 0;
if(level == 9)
{
ChallengeBitArray = 0x1122334455LL;//编程会话模式示例
}
else if(level == 1)
{
ChallengeBitArray = 0x123456789ALL;//扩展会话模式示例
}
seed = (pData[2] << 16) | (pData[1] << 8) | (pData[0]);
ChallengeBitArray = ((ChallengeBitArray << 24) | seed);
for (i = 0; i < 64; i++)
{
B24 = ((ChallengeBitArray >> i) ^ PositionA) & 0x01;
PositionB = (B24 << 23) | (PositionA >> 1);
PositionC = 0;
PositionC = PositionC | (B24 << 3);
PositionC = PositionC | (B24 << 5);
PositionC = PositionC | (B24 << 12);
PositionC = PositionC | (B24 << 15);
PositionC = PositionC | (B24 << 20);
PositionC = PositionC ^ PositionB;
PositionA = PositionC;
}
ret = ret | ((PositionC & 0x000FF0) << 12);
ret = ret | ((PositionC & 0x00F000));
ret = ret | ((PositionC & 0xF00000) >> 12);
ret = ret | ((PositionC & 0x00000F) << 4);
ret = ret | ((PositionC & 0x0F0000) >> 16);
return ret;
}
//自定义的密钥生成函数(示例)
byte GenerateKeyEx(byte iSeedArray[],dword iSeedArraySize,dword iSecurityLevel,char iVariant[],byte ioKeyArray[],dword iKeyArraySize,dword& oSize)
{
dword nRet = 0;
if (iSeedArraySize>iKeyArraySize)
return 1;
switch (iSecurityLevel)
{
case 9://编程会话模式
nRet = SeedToKey1(iSeedArray,9);
break;
case 1://扩展会话模式
nRet = SeedToKey1(iSeedArray,1);
break;
default:
break;
}
ioKeyArray[0] = nRet >> 16;
ioKeyArray[1] = (nRet >> 8) & 0xFF;
ioKeyArray[2] = nRet & 0xFF;
oSize = 3;
return 0;
}
//安全访问解锁函数:level——解锁的安全等级;errorMode——是否要模拟密钥错误;SeedArray_only——是否只申请种子,不进行解锁
long diagUnlockECU(byte level,byte errorMode,byte SeedArray_only)
{
long Resp;
byte gSeedArray[20];
int gSeedArraySize,gSecurityLevel;
char gVariant[50] = "Base_Variant";
char gOption[50] = "option";
byte gKeyArray[20];
int gMaxKeyArraySize = 255;
dword gActualSizeOut;
long errorcode,size;
diagRequest * req;
diagResponse * resp;
byte reqBuffer[20];
req.Resize(2);
diagSetPrimitiveByte(req,0,0x27);
diagSetPrimitiveByte(req,1,level);
gSecurityLevel = level;
testStep("","安全访问申请Seed");
/*请求Seed*/
Resp = diagSendReqAndgetResp(req);
if( Resp == 1 )
{
/*将响应Seed写入参数数组*/
diagGetLastResponse(req, resp);
/*获取Seed&Key的长度*/
size = resp.GetPrimitiveSize();
gSeedArraySize = size - 2;
gActualSizeOut = size - 2;
write("Seed&Key size:%d",size);
resp.GetPrimitiveData(gSeedArray, elcount(gSeedArray));
memcpy_off( gSeedArray,0,gSeedArray,2, gSeedArraySize);
if( SeedArray_only == 1 )
{
memcpy( glob_SeedArray,gSeedArray, gSeedArraySize);//仅申请安全访问种子
return 0xFF;
}
/*计算密钥(使用自定义的密钥生成函数)*/
errorcode = GenerateKeyEx(gSeedArray, gSeedArraySize, gSecurityLevel, gVariant, gKeyArray, gMaxKeyArraySize, gActualSizeOut);
if( 0 == errorcode)
{
/*写入请求参数*/
req.Resize(gActualSizeOut + 2);
reqBuffer[0] = 0x27;
reqBuffer[1] = level+1;
memcpy_off(reqBuffer,2,gKeyArray,0,gActualSizeOut);
if(errorMode == 1)
{
reqBuffer[4] += 1;//模拟制造密钥错误
}
req.SetPrimitiveData(reqBuffer,size);
/*发送密钥*/
testStep("","发送密钥进行解锁");
Resp = diagSendReqAndgetResp(req);
return Resp;
}
else
{
TestStepFail("","密钥计算出错,errorcode:%x",errorcode);
return errorcode;
}
}
else
{
return Resp;
}
}
三、测试场景示例
1、正常的安全访问解锁
long Resp;
/*正常解锁ECU:Level_1,正常解锁,申请种子+发送正确的密钥*/
Resp = diagUnlockECU(0x01,0,0);
if(Resp == 1)
{
testStepPass("正确解锁ECU Level_1,测试成功!");
}
else
{
testStepFail("正确解锁ECU Level_1,测试失败!");
}
2、安全访问秘钥错误
long Resp;
/*异常解锁ECU:Level_1,异常解锁,申请种子+发送错误的密钥*/
Resp = diagUnlockECU(0x01,1,0);
if(Resp == 0x35)
{
testStepPass("第1次错误解锁ECU Level_1,测试成功!");
}
else
{
testStepFail("第1次错误解锁ECU Level_1,测试失败!");
}
上述示例是密钥错误1次,返回NRC35。可以以此进行扩展,比如错误N-1次,返回NRC36,以及错误达到N次锁定后,返回NRC37。
3、连续多次申请解锁,验证种子随机性
long Resp;
byte tmp_SeedArray[6000];
int i,j,k,cmp_cnt;
/*Level_1,仅申请种子*/
for(i = 0;i < 2000;i++)
{
/*27 01申请安全访问种子,1~1000次*/
Resp = diagUnlockECU(0x01,0,1);
if(Resp == 0xFF)
{
if((glob_SeedArray[0] == 0) && (glob_SeedArray[1] == 0) && (glob_SeedArray[2] == 0))
{
testStepFail("","27 01申请安全访问种子,第%d次:获取失败!种子全为0!",i+1);
}
else
{
memcpy_off(tmp_SeedArray,3*k,glob_SeedArray,0, 3);
testStepPass("","27 01申请安全访问种子,第%d次:获取成功!",i+1);
}
}
else
{
testStepFail("","27 01申请安全访问种子,第%d次:获取失败!",i+1);
}
}
for(k = 0;k < 2000;k++)
{
for(i = 0;i < 2000;i++)
{
cmp_cnt = 0;
if(i == k){continue;}
for(j = 0;j < 3;j++)
{
if(tmp_SeedArray[3*k+j] == tmp_SeedArray[3*i+j])
{
cmp_cnt +=1;
}
}
if( cmp_cnt == 3 )
{
testStepFail("","27 01申请安全访问种子,第%d次与第%d次种子完全相同:测试失败!",k+1,i+1);
break;
}
}
if( cmp_cnt < 3 )
{
testStepPass("","27 01申请安全访问种子,第%d次与之前的种子不相同:测试成功!",k+1);
}
}
上述示例是连续申请2000次种子(此处示例的种子为3个字节),存放到tmp_SeedArray[]数组中,最后循环遍历对比,看这申请的这2000次是否有相同种子,以此来验证其随机性。
总结
以上就是如何使用CAPL脚本实现安全访问解锁的讲解,并结合了多个运用场景的实例进行介绍,希望对大家有所帮助。各位可根据本文的示例,结合自己的需求,进行完善和二次开发。