【MCU】用stm32的UID给固件加密(重点在加密)
1、聊一聊
演员这首歌大家应该再熟悉不过了,其中印象最为深刻的歌词是:"简单点,说话的方式简单点......",说话真的是一门技术,同时也是门艺术!
今天跟大家带来的知识不算难,现在非常多MCU都有全球唯一标识码这个东西,可能大家都了解过,不过具体怎么用并没有实际设计过!下面重点对其加密方面的应用跟大家理一理。
2、stm32的标识码UID
对于目前大部分MCU都会存在一个唯一标识码供用户使用,同样stm32也是一样,通过查找对应的数据手册便可以得到该唯一标识码的具体信息。
这里以stm32F103为例,其他型号的stm32性能也可能不存在该唯一标识,具体需要根据对应的数据手册进行查阅,如果存在可能基地址稍有不同。如下图所示:
分析一下:
1 ) stm32的标识码放在了唯一设备ID寄存器里面,一共96个bit也就是12个字节且只能读取。
2 ) 通过手册上的说明可以大致了解到该唯一标识码的应用场景。
3)一般的量产产品都会有一个设备的条码,那么这个唯一的标识码便可以作为条码的一部分来供查找。
4 ) 在通信协议中该唯一标识码可以作为一种标识序列号来进行设备的加载和区分。
5 ) 当然最后就是把其作为一个安全密钥,然后与软件加密算法结合起来以降低固件被恶意复制的风险。
2、读取UID
对于该唯一标识ID,bug菌这里谈两点注意的:
1、唯一标识ID只是stm32里面一种ID,其实一款芯片内部还有很多其他ID,比如设备ID和其他内部组件的ID等;
2、UID一共是96位具有唯一性,而截取中间的几位不一定具有唯一性。
3、对于UID的读取非常简单,上面的手册截图也说明了,可以通过字节、半字和字来进行读取,也就是说可以用8位、16位、32位来读取。
参考Demo:
1uint32_t Unique[3] = {0};
2uint8_t unique[12] = {0};
3
4 int main(void)
5 {
6 uint8_t i;
7
8 Unique[0] = *(uint32_t*)(0x1FFFF7E8);
9 Unique[1] = *(uint32_t*)(0x1FFFF7E8 + 4);
10 Unique[2] = *(uint32_t*)(0x1FFFF7E8 + 8);
11
12 printf("以uint32_t读:\r\n");//插入换行
13 printf("ID 0-31 :%x\r\n",Unique[0]);//插入换行
14 printf("ID 32-63 :%x\r\n",Unique[1]);//插入换行
15 printf("ID 64-95 :%x\r\n",Unique[2]);//插入换行
16
17 for(i = 0 ;i < 12;i++)
18 {
19 unique[i] = *(uint8_t*)(0x1FFFF7E8 + i);
20 }
21
22 printf("以uint8_t读:\r\n");//插入换行
23
24 for(i = 0;i<12;i++)
25 {
26 printf("ID byte%d :%x ",i,unique[i]);//插入换行
27 if(i%4 == 3) printf("\r\n");//插入换行
28 }
29
30 printf("\r\n公众号:最后一个bug\r\n");//插入换行
输出结果:
3、UID加密简易版
之前bug菌整理过一篇单片机解密的文章<【整理】一文带你了解"单片机解密"技术>,对于解密的办法可以说是无比的残忍,其实芯片的加密与解密跟网络安全的攻防是一样的。
所谓:"道高一尺魔高一丈",只有不断的更新加密技术以增加解密成本或许在一定程度上能够遏制不正规解密行为在,下面就先介绍一下UID的一种简易加密方案,为什么说简易呢?可以说修改部分固件实现一个跳转功能就解密了,不过对于一般的小型产品还是能够在一定程度上起到保密效果的。
解释两句:
1 )左边是在芯片外部实现的,可以通过编写一个上位机来自动生成密钥并保存到芯片中的存储介质中。
2 )上面所说的UID与密钥的隐式获取是指 : 其读取过程中的地址等信息不要显式的暴露在固件中,比如上面提到的UID的地址或许是密钥的地址,可以通过数据变换、运算等等进行隐藏。
3 )最后一步如果密钥不一致进行自动清除固件,该思想有点类似于我们平时密码输入,如果输入次数较多就会锁定无法解锁,这样对解密过程造成阻碍。
4、加强版本
对于上面的加密方法其关卡点就一个位置,如果在固件空白区域安插一些跳转指令跳转到正常运行的位置,你的固件就解密了。
所以目前比较常用的是对整个固件进行完整性标识序列与UID组合进行加密的办法。
所以对于未使用的存储区域最好是都填充完,避免被解密者利用。
4、另类版本
出于关卡点过于单一的问题考虑,我们需要进行多处的关卡点处理,同样各个关卡点的复杂度也会给解密者带来困难,每一处关卡点都带有解密信息,相当于每次都会需要判断机密,从而让跳转这种办法失效。
最简单的处理办法就是定义一些宏处理,比如:
1#define Sect_TURE (UIDCODE - SAVECODE + 1)
2#define Sect_FALSE (UIDCODE - SAVECODE)
3
4#define Sect_NUM_1 (UIDCODE - SAVECODE + 1)
5#define Sect_NUM_2 (UIDCODE - SAVECODE + 2)
6......
当然不仅仅只有上面的处理,我们还可以通过替换为其他变量来隐藏一些处理办法,从而达到迷惑解密着的目的。这样我们就可以把加密分布到程序的各个角落,加强了固件的安全性。
不过这样的办法如果放在访问较为频繁的位置,势必会影响系统的性能,如果所使用的芯片性能一般,可以选择部分关键关卡点处理。
好吧,所以一切安全的前提是唯一标识码UID无法被修改,否则也是徒劳,不过既然芯片都是人造的,那肯定就有办法进行解密,只是成本问题。
5、最后小结
本文到这里就结束了,对于MCU的加密和解密是一个永恒的话题,同样对于一个成熟的产品加密也是必须要考虑的技术问题,看看大家还有什么好的MCU加密办法,欢迎大家分享留言讨论!
好了,这里是公众号:“最后一个bug”,一个为大家打造的技术知识提升基地。
推荐好文 点击蓝色字体即可跳转