【嵌入式】使用嵌入式芯片唯一ID进行程序加密实现

目录

一 背景说明

二 原理介绍

三 设计实现

四 参考资料


一 背景说明

        项目程序需要进行加密处理

        考虑利用嵌入式芯片的唯一UID,结合Flash读写来实现

        加密后的程序,可以使得从芯片Flash中读取出来的文件(一般为HEX格式)不能用于其他的芯片。

二 原理介绍

        【1】芯片唯一ID

        芯片提供了器件电子签名,即唯一身份标识。该信息存储在产品唯一身份标识寄存器中:

【嵌入式】使用嵌入式芯片唯一ID进行程序加密实现_第1张图片

        UID寄存器描述如下:

【嵌入式】使用嵌入式芯片唯一ID进行程序加密实现_第2张图片

        其中,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 应该尽量隐蔽,尽量避免在程序中直接出现该值

【嵌入式】使用嵌入式芯片唯一ID进行程序加密实现_第3张图片

                更好的做法可以参考下面的方案:

/**************************************************************************
* 函数名称: 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));  //加密算法
}

四 参考资料

        【1】STM32 的加密实现_stn32加密-CSDN博客

        【2】【STM32+cubemx】0025 HAL库开发:唯一ID获取和简单的程序加密_mcu唯一uid加密-CSDN博客

你可能感兴趣的:(算法,嵌入式硬件,c语言,安全)