系列文章目录
使用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脚本实现安全访问解锁的讲解,并结合了多个运用场景的实例进行介绍,希望对大家有所帮助。各位可根据本文的示例,结合自己的需求,进行完善和二次开发。