芯片加密,主要防止的就是其他人将程序读出后仿制,只要让其破解成本大于研发成本,则可以认为是有效的加密
这里主要讲下载由自己或者信任方控制,芯片下载好程序后才交付给客户或销售,如果需要下载过程也保密,可以参考(远程加密下载):
远程现场升级,自动生成您公司界面的升级软件,省电脑端开发人员,人工智能 - 远程现场升级 =【发布项目程序+程序加密后传输+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[7] _at_ 0x0100; // 存储原始CHIPID每个字节+4的结果
- unsigned char code ID_At200[7] _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[i];
- encrypted200 = ID_At200[i];
-
- // 检查是否符合加密规则
- 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模式下测试通过
CHIPID_CHECK.zip
(403.33 KB, 下载次数: 3)
下面演示一些更为高级的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[7] _at_ 0x0100; // 存储原始CHIPID每个字节+4的结果
- unsigned char code ID_At200[7] _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[0]);
-
- // ----- 2. 分散验证:每次只验证一个字节,共7个字节 -----
- printf("开始ID分散验证(每次验证一个字节)...\r\n");
- for (i = 0; i < 7; i++) {
- // 通过指针间接读取CHIPID(避免汇编直接锁定操作)
- chipid_byte = chipid_ptr[i];
- // 读取ISP写入的加密数据
- encrypted100 = ID_At100[i];
- encrypted200 = ID_At200[i];
-
- // 打印当前验证的字节索引和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>>
- }
- }
复制代码
CHIPID_CHECK (拓展办法).zip
(421.02 KB, 下载次数: 3)
|