带有CHIPID功能的芯片进行ID号加密示例,以及一些额外加密手段介绍
芯片加密,主要防止的就是其他人将程序读出后仿制,只要让其破解成本大于研发成本,则可以认为是有效的加密这里主要讲下载由自己或者信任方控制,芯片下载好程序后才交付给客户或销售,如果需要下载过程也保密,可以参考(远程加密下载):
远程现场升级,自动生成您公司界面的升级软件,省电脑端开发人员,人工智能 - 远程现场升级 =【发布项目程序+程序加密后传输+USB下载】,ID号加密/通过ID号控制下载 国芯人工智能技术交流网站 - AI32位8051交流社区
STC芯片默认就没有留出读出程序的接口,所以芯片下好程序以后,只能通过开盖(磨掉芯片顶部塑封),然后使用墨水染色和电子显微镜观察FLASH后拿到一个二进制文件
这部分操作通常会消耗一万元左右,而CHIPID加密主要防止的就是开盖拿到原始程序以后,直接下载到其他芯片内运行的这一个步骤,使其复制出来的程序到其他芯片内也运行不了
这里顺便讲解一下其他的一些加密手段:
下载口令加密:可以设置一串密码,使其下次下载程序时,需要口令,这样可以避免其他人恶意下载空白/损坏的程序,然后诽谤这个芯片/程序有缺陷
这个密码建议设置的复杂一点(这里仅作演示)
硬件选项加密:通常来讲,芯片会通过无字版本隐藏芯片型号,但是官方ISP的“检测选项”是可以检测到芯片型号的
此时可以通过选择“下次冷启动时,P32和P33都为0时才可下载程序”来阻止使用官方ISP软件检测
这样只要下载好程序后,第二次尝试下载(解密方)就需要这两个端口同时为低电平才能下载,可以在外部利用一个小电阻上拉到高电平,此时使用ISP软件查询芯片型号就会没有反应
从而避免解密方获取到芯片的真实型号
然后是ID号加密,这里需要明确的是,ID号加密仍然可以被破解。因为开盖从FLASH内读取出二进制程序后,是可以转换成汇编代码的。
此时如果ID号验证比较简单,就可能会被查找到关键词然后通过修改汇编指令绕过去。所以这里能做的就是使用多种不同手段进行ID号验证,因为原程序中加验证只是复制粘贴,但是找汇编指令就非常繁琐了
同时,这里推荐ID验证不通过后,依然正常运行,延迟固定时间+随机时间后在关键位置搞破坏/主动死机,创造一种程序运行不稳定的假象,可以有效骗过解密方。因为大部分解密的一看功能都正常就不会继续寻找了ID锁了
首先介绍最简单的ID号验证方式
需要ISP软件勾选ID号加密
这里为了简单演示,就只选择纯加法加密,实际建议同时使用加减,乘除一个奇数(偶数的话和移位区别不大),移位2位及其以上,异或
同时,上下两个部分的加密需要保持不一致,也就是会出现两个加密内容。存储地址是绝对地址。
不能放在太靠前的地方(程序起始位置有启动和中断跳转指令,占用后会有问题),但是也不能放在超出用户程序的区域(太过于明显),这里因为仅作演示,就使用了一些比较规律的地址(0x100和0x200)
(如何查看用户程序空间?在程序文件选项卡左下角,鼠标放在代码长度上即可显示十进制的程序实际占用字节)
然后,在程序内通过_at_方式定义数组进行占用,程序下载完毕后,ISP软件会将加密后的ID号写入对应的地址(需要留出7个byte的空间)
这里建议这个ID号前后也利用_at_定义一些数组,填入一些无意义的数据来进行混淆。这样在汇编代码中,因为没有注释,会非常的难找这部分内容。
程序部分代码:
//<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
// 在此添加用户全局变量定义、用户宏定义以及函数声明
// ID存储占位数组(ISP软件会将加密后的ID数据写入这两个地址)
// 使用_at_关键字指定绝对地址,地址分别为0x0100和0x0200,各占用7字节
unsigned char code ID_At100 _at_ 0x0100;// 存储原始CHIPID每个字节+4的结果
unsigned char code ID_At200 _at_ 0x0200;// 存储原始CHIPID每个字节+8的结果
// ID验证结果标志(全局变量,1表示验证成功,0表示失败)
unsigned char success;
//<<AICUBE_USER_GLOBAL_DEFINE_END>>
////////////////////////////////////////
// 项目主函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void main(void)
{
unsigned char i; // 循环计数器
unsigned char chipid_byte; // 当前读取的CHIPID字节值
unsigned char encrypted100; // 从0x100地址读取的加密值
unsigned char encrypted200; // 从0x200地址读取的加密值
//<<AICUBE_USER_MAIN_INITIAL_BEGIN>>
// 在此添加用户主函数初始化代码
//<<AICUBE_USER_MAIN_INITIAL_END>>
SYS_Init();// 系统初始化(包含串口等)
//<<AICUBE_USER_MAIN_CODE_BEGIN>>
// 在此添加主函数中运行一次的用户代码
// 初始化验证标志为成功(假设通过,遇到失败再置0)
success = 1;
// 依次验证7个字节,验证规则:
// 地址0x100的值 = CHIPID原始值 + 4
// 地址0x200的值 = CHIPID原始值 + 8
for (i = 0; i < 7; i++) {
// 根据循环索引读取对应的CHIPID字节
switch (i) {
case 0: chipid_byte = CHIPID0; break;
case 1: chipid_byte = CHIPID1; break;
case 2: chipid_byte = CHIPID2; break;
case 3: chipid_byte = CHIPID3; break;
case 4: chipid_byte = CHIPID4; break;
case 5: chipid_byte = CHIPID5; break;
default: chipid_byte = CHIPID6; break;
}
// 从预留地址读取加密数据
encrypted100 = ID_At100;
encrypted200 = ID_At200;
// 检查是否符合加密规则
if ((encrypted100 != (chipid_byte + 4)) ||
(encrypted200 != (chipid_byte + 8))) {
success = 0;// 任一字节不匹配,验证失败
break; // 立即退出循环,无需继续检查
}
}
delay_ms(1000);//防止用户打开串口过慢,看不到信息
// 根据验证结果输出对应信息(仅输出成功/失败,不输出具体数值)
if (success) {
printf("ID验证成功!\r\n");
} else {
printf("ID验证失败!\r\n");
}
//<<AICUBE_USER_MAIN_CODE_END>>
while (1)
{
//<<AICUBE_USER_MAIN_LOOP_BEGIN>>
// 在此添加主函数中用户主循环代码
//<<AICUBE_USER_MAIN_LOOP_END>>
}
}
运行结果:
如果未勾选任意一个ID号加密,则验证失败
以下是可以编译运行的CHIPID加密示例程序,已在Ai8051U-32Bit模式下测试通过
下面演示一些更为高级的ID号加密验证方式
1.分散验证,每次只验证一个byte,在程序多个地方验证,给解密制造麻烦
2.间接使用CHIPID地址,防止被地址查询得到(可以通过指针变量运算得到想要的地址),然后再使用,汇编中就不会出现对应的地址
3.验证失败后随机时间后爆出异常,不会立刻异常
以下是正常运行结果
以下是异常运行结果
以下是间接使用CHIPID地址,可以看到汇编代码中没法找到CHIPID0地址0x7EFDE0
以下为高级CHIPID加密办法的完整程序和代码文件
//<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
// 在此添加用户全局变量定义、用户宏定义以及函数声明
// ID存储占位数组(ISP软件会将加密后的ID数据写入这两个地址)
// 使用_at_关键字指定绝对地址,地址分别为0x0100和0x0200,各占用7字节
unsigned char code ID_At100 _at_ 0x0100;// 存储原始CHIPID每个字节+4的结果
unsigned char code ID_At200 _at_ 0x0200;// 存储原始CHIPID每个字节+8的结果
// ID验证结果标志(全局变量,1表示验证成功,0表示失败)
unsigned char verify_failed = 0; // 0=成功, 1=失败
// 用于间接访问CHIPID的指针(避免汇编直接锁定操作)
// 指针初始化时通过两个volatile变量相加得到地址,防止编译器优化为常量
unsigned char volatile far *chipid_ptr;
// 地址拆分部分(volatile防止编译时常量折叠,确保汇编中呈现加法运算)
volatile unsigned long addr_high = 0x7EFD00; // 地址高段
volatile unsigned long addr_low= 0xE0; // 地址低段
//<<AICUBE_USER_GLOBAL_DEFINE_END>>
////////////////////////////////////////
// 项目主函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void main(void)
{
unsigned char i; // 循环计数器
unsigned char chipid_byte; // 当前读取的CHIPID字节值
unsigned char encrypted100; // 从0x100地址读取的加密值
unsigned char encrypted200; // 从0x200地址读取的加密值
unsigned int random_delay; // 随机延迟时间(毫秒)
//<<AICUBE_USER_MAIN_INITIAL_BEGIN>>
// 在此添加用户主函数初始化代码
//<<AICUBE_USER_MAIN_INITIAL_END>>
SYS_Init();// 系统初始化(包含串口等)
//<<AICUBE_USER_MAIN_CODE_BEGIN>>
// 在此添加主函数中运行一次的用户代码
// ----- 1. 初始化间接访问指针(通过运行时加法得到CHIPID地址,防止汇编中出现完整地址)-----
// 使用两个volatile变量相加,编译器必须生成加法指令,不会直接使用常量0x7EFDE0
chipid_ptr = (unsigned char volatile far *)(addr_high + addr_low);
// 用CHIPID的第一个字节作为随机数种子(保证每次上电随机序列不同)
srand(chipid_ptr);
// ----- 2. 分散验证:每次只验证一个字节,共7个字节 -----
printf("开始ID分散验证(每次验证一个字节)...\r\n");
for (i = 0; i < 7; i++) {
// 通过指针间接读取CHIPID(避免汇编直接锁定操作)
chipid_byte = chipid_ptr;
// 读取ISP写入的加密数据
encrypted100 = ID_At100;
encrypted200 = ID_At200;
// 打印当前验证的字节索引和CHIPID原始值(演示用)
printf("验证字节[%d]: CHIPID=0x%02X, 期望0x100=0x%02X(实际0x%02X), 期望0x200=0x%02X(实际0x%02X)\r\n",
i, chipid_byte,
chipid_byte + 4, encrypted100,
chipid_byte + 8, encrypted200);
// 验证规则:0x100地址的值 = CHIPID原始值 + 4
// 0x200地址的值 = CHIPID原始值 + 8
if ((encrypted100 != (chipid_byte + 4)) || (encrypted200 != (chipid_byte + 8))) {
verify_failed = 1;// 标记验证失败
printf("验证失败!字节[%d]不匹配。\r\n", i);
// 注意:失败后不break,继续验证后续字节(演示分散验证)
} else {
printf("验证通过\xfd。\r\n");
}
// 模拟每个字节验证之间的小延时(体现过程)
delay_ms(50);
}
// 最终验证结果汇总
if (!verify_failed) {
printf("ID验证完全成功!程序正\xfd常运行。\r\n");
} else {
printf("ID验证失败!程序将继续运行,但将在随机延时后出现异常。\r\n");
}
// ----- 3. 模拟耗时的流水任务 -----
// 流水任务循环执行,每次打印流水信息并延时
// 如果验证失败,则在一定循环次数后触发随机延迟异常
while (1) {
// 流水任务:模拟处理数据、通讯等耗时操作
printf("流水任务执行中...\r\n");
delay_ms(500);// 模拟耗时500ms
// 如果验证失败,则随机延迟100~1000ms后触发异常
if (verify_failed) {
// 产生100~1000ms之间的随机延迟
random_delay = (rand() % 901) + 100;// rand()%901 得0~900,加100得100~1000
printf("验证失败,随机延时 %d ms 后将出现任务异常...\r\n", random_delay);
delay_ms(random_delay);
// 触发任务异常:打印错误信息并进入死循环(模拟系统崩溃)
printf("\r\n!!! 任务异常 !!! 系统发生不可恢复错误,请重新上电。\r\n");
while (1);// 死循环,程序停止响应
}
}
//<<AICUBE_USER_MAIN_CODE_END>>
while (1) // 实际不会执行到这里,因为上面while(1)已包含
{
//<<AICUBE_USER_MAIN_LOOP_BEGIN>>
// 在此添加主函数中用户主循环代码
//<<AICUBE_USER_MAIN_LOOP_END>>
}
}
页:
[1]