目录
一 背景说明
项目程序需要进行加密处理。
考虑利用嵌入式芯片的唯一UID,结合Flash读写来实现。
加密后的程序,可以使得从芯片Flash中读取出来的文件(一般为HEX格式)不能用于其他的芯片。
二 原理介绍
【1】芯片唯一ID
芯片提供了器件电子签名,即唯一身份标识。该信息存储在产品唯一身份标识寄存器中:
UID寄存器描述如下:
其中,UID1、UID2均为16位,UID3为32位,UID4为32位。
这样就可以从0x0x1FFFF7E8地址读取3个32bit的数据,获取它的唯一ID。
【2】程序加密逻辑说明
在讲解简单的程序加密方法之前,先了解一下通常的MCU程序复制方法。
通过烧写器(或者其他手段),将MCU内部程序存储区中的内容全部读出,就获取了烧写文件。如果烧写文件没有被加密,则可以直接将它烧写到别的MCU中运行,就完成了复制。
有没有办法可以使得对方即使获取了烧写文件,也不能直接烧写到别的MCU中运行呢?
我们可以利用芯片的唯一ID来实现一种简单的程序加密方法。由于每个芯片的唯一ID是不同的,我们只要在程序中检查ID的合法性即可,主要的步骤如下:
(i)程序首次运行时,读出芯片的唯一ID;
(ii)将ID使用加密算法,计算出一个特殊值,写入到非易失存储区中(需要掉电能保存);
(iii)以后每次运行时,读出芯片唯一ID,通过算法计算后,与非易失存储区中保存的值比对,如果一致,则正常运行;如果不对,停止运行。
由于每个芯片中的ID是不同的,由加密算法算出的值也是不同的,所以,即使有人获取了我们的烧写文件,烧写到另一片MCU中,由于ID不同,最后也会比对出错,无法执行。
三 设计实现
【1】编写获取并加密ID的接口:
#define UID_BASE (0x1FFFF7E8)
/**************************************************************************
* 函数名称: getEncryptID
* 功能描述: 获取并加密ID
**************************************************************************/
u32 getEncryptID(u32 *p_id)
{
p_id[0] = *(vu32*)(UID_BASE);
p_id[1] = *(vu32*)(UID_BASE + 4);
p_id[2] = *(vu32*)(UID_BASE + 8);
return ((p_id[0] >> 3) + (p_id[1] >> 1) + (p_id[2] >> 2)); //加密算法
}
【2】编写判断加密ID的接口:
const u32 uid_save = 0xFFFFFFFF; //CPUID保存
/**************************************************************************
* 函数名称: judgeEncryptID
* 功能描述: 判断加密ID
**************************************************************************/
u8 judgeEncryptID(void)
{
u32 t_encid;
u32 t_cpuid[3];
u32 t_addr = 0;
u32 t_flashid;
t_encid = getEncryptID(t_cpuid); //读本机UID
t_addr = (u32)&uid_save;
if(*(vu32 *)t_addr == 0xFFFFFFFF) //第一次烧写将加密后的UID写入FLASH
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);
FLASH_ProgramWord(t_addr, t_encid);
FLASH_Lock();
}
t_flashid = *(vu32 *)t_addr; //第一次上电存完了之后需要再读一遍
return (t_flashid == t_encid);
}
【3】主函数初始化中执行上面的判断:
void main(void)
{
//...
//判断加密UID(若不匹配,则报错并陷入死循环)
if(! judgeEncryptID())
{
while(1);
}
while(1)
{
//主循环
}
}
【注意点】:
(i)注意 const u32 uid_save 的类型定义,我使用的 keil 环境中不能加 volatile 修饰,如果加了会被定义到RAM区,而非Flash区,这样起不到加密的效果。
如果用的是IAR环境,可能必须加 volatile 修饰,同时工程选项Options->Debugger->Download中选择: use flash loader。不这样做的后果是按速度优化编译,再判断加密值,不会重新读取加密值,导致判断出错;
(ii)注意 const u32 uid_save 的初始化值定义为全F,和擦除后的状态一致,所以程序中省略了擦除的操作,可以直接写入;
(iii)唯一ID存储地址 0x1FFFF7E8 应该尽量隐蔽,尽量避免在程序中直接出现该值:
更好的做法可以参考下面的方案:
/**************************************************************************
* 函数名称: getEncryptID
* 功能描述: 获取并加密ID
**************************************************************************/
u32 getEncryptID(u32 *p_id)
{
volatile u32 UID_BASE;
UID_BASE = 0x20000006; //让逆向的人误以为是ram变量
UID_BASE -= 0x800;
UID_BASE -= 0x1e; //等于id的基地址0x1FFFF7E8
p_id[0] = *(vu32*)(UID_BASE);
p_id[1] = *(vu32*)(UID_BASE + 4);
p_id[2] = *(vu32*)(UID_BASE + 8);
return ((p_id[0] >> 3) + (p_id[1] >> 1) + (p_id[2] >> 2)); //加密算法
}
四 参考资料
【2】【STM32+cubemx】0025 HAL库开发:唯一ID获取和简单的程序加密_mcu唯一uid加密-CSDN博客