首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

STM32 的加密实现方法

STM32 的加密实现方法

本文目标是对运行于STM32的嵌入式代码程序进行加密,编译环境为Keil。
, i3 p9 Q- _, L. C! }) m
一.STM32Flash组织5 c  X' m) ?6 G2 M* `8 R+ z
2 j% m# h) ~* G4 n: l

7 {7 G* L+ F3 y1 g+ F
        STM32的Flash包括主存储器(HD版本,512KB)+信息块。信息块包括2KB的系统存储器(用于系统自举启动代码)和16字节的选项字节(8个字节数据+8个字节数据的反码)。

二、STM32读保护
        STM32读保护是通过设置RDP选项字节,然后在系统重新复位加载了新的RDP选项字节后启动的。当保护字节被写入相应的值以后:* {1 v1 t; s" T. k+ ~0 V8 m

●通过从内置SRAM或FSMC执行代码访问主闪存存储器的操作,通过DMA1、DMA2、JTAG、SWV(串行线观察器)、SWD(串行线调试)、ETM和边界扫描方式对闪存的访问都将被禁止。4 u: M" B/ @" r0 l

●只允许从用户代码中对主闪存存储器的读操作(以非调试方式从主闪存存储器启动)。4 \: A$ \0 Z" I& f' u# C& F
) T( H  _/ h; o
●第0~3页(小容量和中容量产品),或第0~1页(大容量和互联型产品)被自动加上了写保护,其它部分的存储器可以通过在主闪存存储器中执行的代码进行编程(实现IAP或数据存储等功能),但不允许在调试模式下或在从内部SRAM启动后执行写或擦除操作(整片擦除除外)。

●所有通过JTAG/SWD向内置SRAM装载代码并执行代码的功能依然有效,亦可以通过JTAG/SWD从内置SRAM启动,这个功能可以用来解除读保护。当读保护的选项字节转变为存储器未保护的数值时,将会执行整片擦除过程。

●可以使用系统启动程序解除读保护(此时只需执行系统复位即可重新加载选项字节),芯片自动擦除Flash所有内容。
% M' Y: w, z9 E$ N# e/ Z2 C4 y
三.STM32的加密
/ V% |8 ], c6 w5 D
1.使用系统启动程序STM32 Flash Loader demonstrator将Flash设置为读保护。" f1 z+ ~; L& _4 {4 p
所有以调试工具、内置SRAM或FSMC执行代码等方式对主存储器访问的操作将被禁止,只允许用户代码对主Flash存储器的读操作和编程操作(除了Flash开始的4KB区域不能编程)。用户代码允许自主编程可以实现IAP或者数据存储等功能。
这样破解者将不能用调试工具、内置SRAM或者FSMC执行代码等方式读出Flash中的代码。破解者也不能使用系统启动程序读取代码,因为要解除读保护,将执行整个芯片的擦除操作。

2.主程序中使用设备ID保护
    即使将Flash设置为读保护,破解者也可以通过IAP下载自己的一段小程序,从而读出Flash中的内容。因此,还需要利用设备的唯一ID进行加密保护。在主程序中,加入对设备唯一ID的检测,这样即使破解者读出了芯片中的二进制码,也不能用这个二进制码去复制新的器件。具体实现方法:

   (1)在应用程序中定义1个(32位甚至更多)const变量,变量值全为0xFF。每次启动程序时,检查const变量值如果全为0xFF,就读器件的唯一ID,通过Flash编程写入该const变量中(因为全是0xFF,所以可以编程写入)。! G0 E$ ?& T% A+ d

    (2)在程序中多个地方检查const变量,如果变量值不为0xFF并且与设备ID不一致,就执行与功能无关代码(比如自擦除)。

    这样,即使破解者读出了芯片中的二进制码,因为这份二进制码包含了设备唯一ID,具有唯一性,所以不能复制到其他芯片中。
    为了避免破解者利用反汇编,根据芯片ID数据在二进制文件中查找对应相同数据的位置从而破解,可以将ID拆散成不同的组合,并且写到不同且不连续的地方。更进一步,可在程序中检测多份这样的分散ID,以增加反汇编的难度。或者将CPUID进行加密,Flash中存储加密后的结果。: H" r4 c4 \! }' T! M

四.程序加密的实现 . C+ C: k% G# s" @  [
//加密后的CPUID  c3 `4 C5 I) B' n/ s
volatile const static uint32 CPUIDEncrypt = 0xFFFFFFFF;& j4 P' G) `0 `( q

//写入加密数据
void WriteEncrypt(void)
{
    //第一次烧写:将UID写入到Flash中$ W% s/ _; k4 w9 {" b
    if(CPUIDEncrypt==0xFFFFFFFF)3 [% g5 i" X, M# r
   {
        uint32_t CpuID[3];      
        //获取CPU唯一的ID
        CpuID[0]=*(vu32*)(UID_BASE);! [2 [3 b6 R6 U( S" A
        CpuID[1]=*(vu32*)(UID_BASE+4);% v9 L; Y1 `( m2 u
        CpuID[2]=*(vu32*)(UID_BASE+8);        : v' \) X# f0 |" \& S! v
  }# M% G7 w  {5 g" p* l5 h
        //加密算法,很简单的加密算法" t7 s2 \* }! V: m/ {5 Q
        uint32_t EncryptCode=(CpuID[0]>>3)+(CpuID[1]>>1)+(CpuID[2]>>2);   ' h% |, s) a2 i. p# f
        FLASH_Unlock();
        FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);- H$ i( z1 ]# i0 O
        FLASH_ProgramWord((uint32_t)&CPUIDEncrypt, EncryptCode);9 {: D' p, Y3 n* Z* ]
        FLASH_Lock();
    }
}( L6 W- A/ [3 c4 u
: v' t* Z6 y& Z! H0 H2 u6 [; X
//判断加密2 h9 \# I  z' u1 J4 T
bool JudgeEncrypt(void)
{      
    uint32_t CpuID[4];      
    //获取CPU唯一的ID3 q: h5 Y" k* U3 g
    CpuID[0]=*(vu32*)(UID_BASE);% w  m; C; s% e
    CpuID[1]=*(vu32*)(UID_BASE+4);
    CpuID[2]=*(vu32*)(UID_BASE+8);    4 N( p3 j! H- G0 o$ V2 d
    //加密算法,很简单的加密算法
    CpuID[3]=(CpuID[0]>>3)+(CpuID[1]>>1)+(CpuID[2]>>2);   5 l3 [  m3 r) F2 Y/ I$ ^
    //检查Flash中的UID是否合法
    return (CPUIDEncrypt == CpuID[3]);
}  a0 _; m6 f: A6 O+ k

1、将写入加密数据和判断加密两个功能分开。写入加密在PrsCtrlTask开始的地方;而判断加密分布到程序的各个角落。* p8 j2 i, M) R$ U

2、非常重要的是CPUID加密值的定义一定要加“volatile”类型:
volatile const static uint32 CPUIDEncrypt = 0xFFFFFFFF;6 }, Q$ D9 d' R( [, ?& S0 @
否则按速度优化编译,在判断加密值,不会重新读取加密值,导致判断出错。

. Q( u. y! d4 I8 A3 d
3、在工程选项Options->Debugger->Download中选择: use flash loader( x( Q; n3 j# K/ t6 o0 K1 M0 H
否则主程序中对Flash编程将不成功。
继承事业,薪火相传
返回列表