STM32加密

1.概述:

学习stm32加密技术,要先知道stm32系列的单片机上,一个简单的单片机程序是怎样的(C);要知道为什么需要加密,知道有哪些加密技术;接着我们要先知道别人一般是怎么盗取我们的程序,才知道在哪些途径或地方要去加密;

2.网课教程加密教学:

1.生成密钥 (出厂时运行一次):

读取UID,应用MD5算法,得到一个预存值,再把它作为种子去生成随机数,并写入Flash。

2.校验密钥 (每次启动时运行):

重复计算过程,与Flash中的预存值比对,不一致则擦除程序(或进入死循环或安全模式)。

3.开启RDP读写保护

这个教学防范的是:

生成密钥,需要防破解;校验密钥,是为了防止攻击者把你的固件原封不动地复制到另一颗芯片上运行;开启RDP读写保护,防止固件被读取。

即:

首次生成

flash解锁,读取uid,对uid加盐,写入flash,flash上锁

每次运行检查

读取指定位置uid,按秘钥公式计算结果,对比,(设置RDP保护)=> 成功/失败

3.对课程的补充

你的总结非常准确,抓住了课程的核心三步:生成密钥 → 校验密钥 → 开启 RDP 读保护。而且你对每一步的防范目标也理解到位:防止破解、防止固件被复制到别的芯片、防止固件被直接读取。

在此基础上,我再补充几个关键细节和常见遗漏,让你的总结更完整、更实用。


✅ 你的总结已经正确的部分

步骤 操作 防范目标
1. 生成密钥(出厂一次) 读取 UID → MD5 得到固定值 → 作为种子生成随机数 → 写入 Flash 让每颗芯片拥有唯一密钥,防止批量克隆
2. 校验密钥(每次启动) 重新计算 UID + MD5,与 Flash 中的预存值比对,不一致则死循环 防止固件被直接复制到另一颗 UID 不同的芯片上运行
3. 开启 RDP 读保护 设置选项字节为 RDP Level 1 或 Level 2 防止通过调试器(ST-Link/J-Link)读出 Flash 中的固件和密钥

📌 需要补充完整的几个技术细节

1. 为什么生成密钥时要用"随机数"?

你提到"把 MD5 结果作为种子去生成随机数并写入 Flash",这看似多了一步,其实有实际意义:

  • 直接存储 MD5(UID) 是可以的,但攻击者如果知道你的算法,可以自己计算出来(但无济于事,因为 UID 不同)。

  • 生成随机数再存储 通常是为了后续使用该随机数作为对称加密的密钥(比如 AES 加密通信数据)。此时 MD5 只是用来验证 UID 是否正确,真正的加密密钥是随机生成的,增加破解难度。

  • 简单场景下,完全可以省略随机数,直接存储 MD5(UID + 自定义常数) 作为比对值。

2. RDP 的三个级别及注意事项
  • Level 0:无保护(调试接口全开)。

  • Level 1 :读保护开启,调试接口无法读取 Flash;要解除保护会触发 mass erase (全片擦除),固件和密钥都被清空。这是量产产品最常用的级别

  • Level 2 :调试接口永久关闭,且无法降级到 Level 1 或 0。芯片不再接受任何调试连接。一旦设置,芯片几乎无法再被读取或改写(除非使用特殊的后门,一般不存在)。适合极高安全要求的设备。

⚠️ 重要 :开启 RDP Level 1 或 2 后,你不能再通过调试器烧录新固件 (除非先解除保护并擦除芯片)。所以必须在烧录完整合了校验代码的固件后,最后一步再通过代码设置 RDP 位,或者使用烧录工具在烧录完成后自动设置。

3. 校验失败的处理:不要只死循环

简单的 while(1); 很容易被攻击者通过复位或电源干扰绕过。更可靠的做法:

  • 进入硬件陷阱 :例如随机延时后调用 NVIC_SystemReset() 不断复位,或者故意触发硬件错误(除零、非法地址访问)。

  • 功能降级:不直接死机,而是禁用所有关键功能(比如输出错误数据、停止电机驱动),让设备看起来正常但实际不可用。

  • 多次校验:在程序的不同位置、不同时机反复校验(例如在中断服务函数、定时器回调里也加校验)。

4. 防止校验代码本身被跳过或修改

即使有 RDP,攻击者如果通过故障注入(如电压毛刺、激光照射)让 CPU 跳过校验指令,依然可能运行你的程序。因此:

  • 将校验代码放在 PCROP(专有代码读保护) 区域,该区域的代码只能执行,不能通过调试器或 CPU 数据读访问读取。这样攻击者无法分析你的校验逻辑。

  • 使用校验和:对整个 Flash 或关键代码段计算 CRC,与预先存储的值比对,防止代码被篡改。

5.校验这一步,是为了防止攻击者把你的固件原封不动地复制到另一颗芯片上运行。

下面我用一个场景来解释它的作用,以及它为什么能起到防范作用。

🎯 校验的核心目的:实现"一机一码"绑定

你的方案本质上是让每个芯片的固件和它唯一的UID绑定

  1. 出厂时(你的编程器操作)

    • 读取芯片A的UID → 经过MD5+随机数种子 → 生成一个独一无二的密钥Key_A。

    • 把Key_A写入芯片A的Flash中。

  2. 运行时(芯片A自己执行)

    • 再次读取自己的UID → 用完全相同的算法计算出Key_A'。

    • 将Key_A'与Flash中预存的Key_A进行比较。

    • 如果相等,说明这颗芯片确实是"原装"的,程序继续运行。

    • 如果不相等,进入死循环或安全模式,程序不工作。

🛡️ 它能防范什么?

它能有效防止"固件克隆"攻击。

假设攻击者:

  • 通过某种手段(比如从你的工厂废品中)读出了芯片A的完整Flash内容(包含了你的程序代码 + 预存的Key_A)。

  • 然后,他买了一批空白的芯片B、C、D......,想把读出的固件直接烧录进去。

结果会怎样?

  • 芯片B启动后,会读取自己的UID_B,计算出Key_B'。

  • 但它Flash里存的是Key_A(从芯片A复制过来的)。

  • Key_B' ≠ Key_A,校验失败 → 芯片B拒绝运行。

攻击者得到的固件,只能在芯片A上运行,换任何其他芯片都无效。这就等于让你的程序**"锁死"在原始芯片上**,无法批量克隆。

🧪 那它防不住什么?

你的校验方案也有明显的弱点,主要是下面两种攻击:

  1. 暴力跳过校验 :攻击者拿到你的固件二进制后,不修改密钥,而是直接用反汇编工具找到校验失败的跳转指令(比如if (wrong) goto deadloop;),把它改成goto normal_run;,然后重新烧录。这样校验就被"阉割"了。

    • 如何应对 :不要把校验做成一个明显的if分支。可以把校验结果作为后续解密的密钥,或者让校验失败后随机死机、破坏关键数据,增加定位难度。
  2. 算法逆向 :攻击者如果反编译了你的固件,看到你是用MD5(UID) -> srand() -> rand(),他完全可以自己写个小程序,输入任意UID就能算出对应的Key。这样他就能批量生成合法密钥,然后写入克隆芯片。

    • 如何应对 :不要使用标准库的srand/rand,它的序列是公开的。改用你自己设计的、不可逆的混淆算法,或者直接使用硬件加密引擎(如STM32的AES)结合UID生成密钥,让攻击者即使看到代码也无法轻易逆向出从UID到Key的映射关系。
✅ 总结:校验这一步到底有没有用?

有用,而且非常必要。 它是实现"一机一码"防复制的核心逻辑。没有这个校验,你的密钥生成得再复杂也是白费------因为程序根本不会去检查密钥是否正确。

但是,仅仅有这个校验还不够。你必须配合:

  • 读保护(RDP Level 1或2):防止攻击者轻易读出你的固件二进制,从而无法分析你的校验算法。

  • 代码混淆/防篡改:让攻击者难以通过简单修改二进制来跳过校验。

  • 强密钥派生算法:让从UID到Key的计算不可逆、不可预测。

你的方向是正确的,只要在这个基础上加固算法和物理防护,就能有效提升盗取成本。


🔒 更完整的加密流程(课程可能未深入讲,但建议了解)

text

复制代码
┌─────────────────────────────────────────────────────┐
│ 1. 芯片出厂/首次烧录                                  │
│    - 烧录 Bootloader + 应用程序(不含校验数据)        │
│    - 运行一次密钥生成函数:                            │
│        key = MD5(UID + 自定义常数)                    │
│        可选:生成随机数 enc_key                       │
│        将 key 和 enc_key 写入 Flash 的特定扇区         │
│    - 开启 RDP Level 1                                 │
├─────────────────────────────────────────────────────┤
│ 2. 每次上电运行                                       │
│    - 读取 UID,重新计算 key'                          │
│    - 与 Flash 中的 key 比对                           │
│    - 不一致 → 进入死循环/复位/功能降级                 │
│    - 一致 → 正常启动,可选使用 enc_key 进行通信加密    │
│    - 运行时随机位置再次校验(防止被跳过)              │
└─────────────────────────────────────────────────────┘

🎯 你的总结最终版(建议)

尚硅谷 STM32 加密课程核心思想:利用芯片唯一 ID(UID)结合 MD5 哈希算法,为每颗芯片生成独一无二的密钥,并在每次启动时校验该密钥。若密钥不匹配(说明固件被复制到其他芯片),则拒绝运行。同时开启 RDP 读保护,防止攻击者通过调试接口直接读取 Flash 中的固件和密钥。此外,还可以配合 PCROP、校验和、运行时多点校验等方法增强安全性。


📖 扩展知识:攻击者可能的盗取途径(帮助你理解为何要这样加密)

攻击方式 原理 对应防御措施
调试接口读取 使用 ST-Link 直接连接 SWD/JTAG 读出 Flash RDP 读保护
固件复制到同型号空片 把读出的固件烧录到另一颗 STM32 上 UID 校验(新芯片 UID 不同,校验失败)
反汇编 + 跳过校验 用 IDA Pro 等工具分析固件,找到校验函数并 patch 掉 PCROP (隐藏校验代码)、校验和(防止修改)
故障注入 在 CPU 执行校验指令时用电压毛刺使其跳过 多次校验随机延时校验硬件安全单元
物理拆片读取 去除芯片封装,用探针直接读取 Flash 单元 RDP Level 2 + 顶层金属屏蔽(仅部分高端 MCU 支持)

你的总结已经抓住了精髓,上面补充的细节可以帮助你更深入地理解并实际落地。如果还需要了解如何编写代码实现 UID 读取、MD5 计算或 RDP 设置,我可以提供示例。

4.示例代码

以下代码演示了完整的出厂密钥生成与运行时的校验流程。为防篡改,建议将密钥存储在OTP区域或受保护的Flash扇区。

c

复制代码
#include <string.h>

#define KEY_STORAGE_ADDR  0x0800FC00   // Flash存储密钥的地址

// 生成16字节MD5密钥(基于UID + 自定义盐)
void Generate_Key(uint8_t *output_key)
{
    uint32_t uid[3];
    STM32_Get_UID(uid);                     // 读取96位UID
    
    uint8_t buffer[12 + 8] = {0};           // 12字节UID + 8字节自定义盐
    memcpy(buffer, uid, 12);
    memcpy(buffer + 12, "YourSalt", 8);     // 自定义盐
    
    Compute_MD5(buffer, sizeof(buffer), output_key);  // output_key为16字节
}

// 出厂时执行一次:生成密钥并写入Flash
void Factory_Key_Generate(void)
{
    uint8_t key[16];
    Generate_Key(key);
    
    HAL_FLASH_Unlock();
    // 先擦除存储扇区
    FLASH_Erase_Sector(KEY_STORAGE_ADDR);
    // 写入密钥
    for (int i = 0; i < 16; i++) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, KEY_STORAGE_ADDR + i, key[i]);
    }
    HAL_FLASH_Lock();
}

// 每次启动时执行:校验密钥,不一致则死机
void Check_Key(void)
{
    uint8_t calc_key[16];
    uint8_t stored_key[16];
    
    Generate_Key(calc_key);
    memcpy(stored_key, (uint8_t *)KEY_STORAGE_ADDR, 16);
    
    if (memcmp(calc_key, stored_key, 16) != 0) {
        while (1) {
            // 校验失败,进入死循环或触发复位
        }
    }
}

// 主函数示例
int main(void)
{
    HAL_Init();
    
    // 判断是否首次运行(例如检测特定Flash标志位)
    if (Is_First_Run()) {
        Factory_Key_Generate();   // 生成并写入密钥
        Set_First_Run_Flag();     // 标记已初始化
    }
    
    Check_Key();                  // 每次启动校验
    
    // 此处可设置RDP保护(通常最后开启)
    // STM32_Set_RDP_Level(OB_RDP_LEVEL_1);
    
    while (1) {
        // 正常应用程序
    }
}

⚠️ 重要提醒

  • RDP设置时机:建议在固件全部烧录、功能验证无误后,作为量产的最后一步开启RDP。一旦开启,调试接口将被禁用,后续无法通过ST-Link/J-Link读取或烧录,升级固件需通过Bootloader或空中升级(OTA)实现。

  • 密钥存储位置:推荐使用OTP(One-Time Programmable)区域存储密钥,该区域只能编程一次且不可擦除,安全性更高。

  • RDP Level 2风险:开启后永久禁用调试接口且不可逆转,量产前务必测试所有功能,否则芯片将变砖无法调试。

以上示例代码可根据具体STM32系列微调。如需更高级的加密(如AES硬件加密),可参考STM32的CRYP模块或安全固件安装(SFI)等机制。

5.AES加密

1.概念和实现

高级加密标准(AES)可以理解为一种能将你的数据锁起来,并且目前公认很难被破解的"高强度锁"。它是一种对称加密算法,意思是加密和解密使用的是同一把"钥匙"(密钥)。

🔒 AES 的核心:密钥长度

AES的安全性很大程度上取决于"钥匙"的长度,主要分为三种:

  • AES-128:使用128位密钥,是目前最常见的选择。它在安全性和性能之间取得了良好的平衡。

  • AES-192:使用192位密钥,安全性比AES-128更高,但速度稍慢,适用于对安全有更高要求的场景。

  • AES-256:使用256位密钥,是AES家族中最安全的版本,也是许多政府和金融机构的加密标准。

选择哪种,本质上是根据你的具体应用场景,在安全性和加解密速度之间做权衡

⚙️ AES 是如何工作的?

AES的工作过程可以拆解为三个步骤,你可以把它们想象成对数据"加扰"的三个阶段

  1. 数据分块:AES先把要加密的数据分成一个个固定大小的小块,每个块是128位(16字节)。然后,它会对每个小块分别进行加密处理。

  2. 密钥扩展:AES会将原始密钥扩展成一系列不同的子密钥,用于后续的每一轮加密,这个过程叫作"密钥扩展"。

  3. 多轮加密:对每个数据块进行若干轮(10、12或14轮,取决于密钥长度)的复杂运算。每一轮都包含替换、移位、混合和密钥加等操作,让原始数据和密钥充分"搅"在一起。

🗝️ 选择哪种工作模式?

AES在处理多个数据块时,需要指定一种"工作模式"。这决定了如何将独立的块串联起来。以下是两种最常见模式的对比:

特性 ECB(电子密码本模式) CBC(密码块链模式)
原理 每个数据块独立加密,相同明文产生相同密文 每个数据块加密前先与前一个密文块进行异或运算
优点 简单、高效,可并行处理 安全性高,相同明文产生不同密文
缺点 不安全,会泄露明文模式 加密过程无法并行,需要初始化向量
推荐度 强烈不推荐 推荐使用

此外,还有CTR(计数模式)、GCM(伽罗瓦/计数器模式)等。其中GCM模式很受欢迎,因为它不仅能加密,还能同时做完整性校验。

🚀 在STM32上实现AES

STM32提供了两种实现AES的方式:

1. 硬件加速器(AES外设)

很多STM32系列(如STM32L4, STM32H7等)内部集成了专用的AES硬件加速器。你可以把它理解为一个专门为AES算法设计的硬件模块。

  • 极致性能:硬件加速器比纯软件实现快10到20倍。例如,用128位密钥加密一个16字节的数据块,硬件加速器仅需约51个时钟周期。

  • 低功耗:硬件完成同样任务消耗的能耗远低于CPU。

  • 简化主控:CPU只需将数据和密钥交给AES外设,然后就可以去处理其他任务,无需全程参与复杂的计算。

2. 软件实现

如果你的STM32型号没有AES硬件外设,也可以使用纯软件算法库(如mbedTLS)来实现AES。这种方式更灵活,不依赖特定硬件,但加解密速度会慢很多,并且会占用CPU大量时间。

🛡️ SAES:更安全的AES版本

在一些较新的STM32型号(如STM32U5, STM32H5)中,ST公司还提供了一个更安全的AES版本,称为SAES(Secure AES)

  • 抵抗侧信道攻击:AES硬件虽然快,但运行时可能会通过功耗、电磁辐射等方式"泄露"密钥信息。SAES通过硬件设计上的优化,可以有效抵抗这种攻击。

  • 硬件密钥 :SAES可以直接使用芯片内部硬件派生的密钥,例如基于唯一ID派生的DHUK 。这意味着你的密钥永远不会出现在软件中,从根本上防止了密钥被读取的可能。

🔐 安全实践:如何管理好你的密钥?

一个安全的加密系统,密钥的管理比加密算法本身更重要

  1. 绝不硬编码千万不要在代码中直接将密钥写死为常量。攻击者一旦获得你的固件,就能轻易找到这些密钥。

  2. 密钥派生:可以利用每颗芯片唯一的96位UID,通过特定算法(如哈希运算)派生出唯一的密钥。这样,每台设备的密钥都不同,可以防止固件被批量克隆。

  3. 安全存储 :对于SAES这类硬件,可以直接使用硬件派生的密钥,避免密钥暴露在软件中。对于普通AES,应结合使用PCROP(专有代码保护) 等功能,将存储密钥的Flash区域保护起来,禁止任何形式的读取。

  4. 安全启动(Secure Boot):这是将AES用于固件保护最经典的应用。简单来说,程序启动时会先运行一个不可更改的"引导程序"(Bootloader),它负责将Flash中加密的应用程序代码进行解密,然后再跳转执行。这个过程确保了只有合法的固件才能被解密和运行。

2.通俗来讲

用最生活化的方式来理解 AES 加密。

你可以把 AES 想象成一个 "超级搅拌机" ,把你想保护的信息(比如一段文字、一张图片)放进去,加上一把"钥匙",一按开关,出来就变成一堆谁也看不懂的"乱码"。只有拿着同一把"钥匙"的人,才能把这堆"乱码"重新变回原来的信息。


🥤 第一步:把信息切成小块

AES 这个搅拌机一次只能处理固定大小的原料。它会把你所有的信息,像切香肠一样,切成 16 厘米(16字节) 长的段。一段一段地搅。


🔑 第二步:你需要一把钥匙

这把钥匙就是你的"密码"。AES 提供了三种长度的钥匙:

  • 128 位的钥匙:相当于一个 16 位的数字密码(强度足够日常用)

  • 192 位的钥匙:相当于 24 位密码(更安全)

  • 256 位的钥匙:相当于 32 位密码(银行/军方级别,极难破解)

钥匙越长,搅拌的轮数越多,也就越安全,但速度会稍微慢一点。


🔄 第三步:搅拌过程(这就是加密的核心)

把一段 16 字节的原料和你的钥匙一起放进搅拌机,它会重复做 10 ~ 14 轮(取决于钥匙长度)的混合操作,每一轮都包括:

  1. 替换:把每个字节按照一张秘密的替换表,换成另一个字节(就像用摩斯密码替换字母)。

  2. 移位:把整段数据里的字节像排队一样,左右移动位置。

  3. 混合:把这一行和那一列的数据混合起来,让每个输出字节都依赖于所有输入字节。

  4. 加钥匙:把这一轮专用的子钥匙混进去。

每一轮之后,数据都变得更加"面目全非"。经过十几轮之后,你根本看不出它和原来的数据有任何关系。


🧩 工作模式:怎么处理多段数据?

因为信息被切成了很多段,每一段都用同样的钥匙单独搅拌,如果不做额外处理,那么两个相同的段搅拌后会得到相同的乱码,这就可能泄露信息。所以 AES 提供了几种"连接方式":

  • ECB 模式(最差) :每一段独立搅拌,完全相同的内容出来完全相同的乱码。不推荐,因为攻击者能看出规律。

  • CBC 模式(常见且安全):先把当前段和上一段的搅拌结果混在一起,再放进搅拌机。这样即使原始内容相同,出来的乱码也不一样。第一段因为没有"上一段",需要额外加一个随机数(叫"初始化向量")。

除了 CBC,还有更先进的 GCM 模式,它不仅能加密,还能在搅拌时顺便贴上一个"防伪标签",用来验证数据没有被篡改。


🛡️ AES 到底有多安全?

目前,没有公开的方法能直接破解 AES。最可行的攻击方式是"暴力尝试",也就是用所有可能的钥匙去试。

  • 对于 AES-128:用全世界最快的计算机去试,需要 上亿亿亿年

  • 对于 AES-256:那是给担心量子计算机出现后的人准备的,安全级别更高。

所以只要你的钥匙足够随机(不是 "123456" 这种),并且妥善保管,AES 就是目前最可靠的加密方法之一。


🧪 举个生活中的例子

你要寄一个箱子给朋友,但怕快递员偷看:

  1. 原始内容:一张写有"明天见面"的纸条。

  2. AES 加密 :你把纸条放进一个 AES 搅拌机,用你们约定好的钥匙(比如"苹果123")启动机器。出来的是一堆看起来像"#%¥&*...8f3a"的乱码。

  3. 运输:快递员看到这堆乱码,完全不知道是什么。

  4. 解密 :你朋友拿到箱子后,放进同一个 AES 反向搅拌机,输入相同的钥匙"苹果123",机器就把乱码恢复成"明天见面"。


✅ 总结一句话

AES 就是把你的数据切成小块,用一把钥匙反复"搅拌、打乱、混合"很多轮,最后变成一堆没有钥匙就绝对解不开的乱码。它是目前全世界应用最广泛、最可靠的加密算法之一。

相关推荐
Wave8452 小时前
STM32 启动模式与固件更新机制 (底层深度解析)
stm32·单片机·嵌入式硬件
EVERSPIN2 小时前
国产异步SRAM单片机外扩专用存储芯片
单片机·嵌入式硬件·sram·国产sram·异步sram·国产异步sram
WeeJot嵌入式2 小时前
【中断】深入了解中断
单片机·嵌入式硬件
潜创微科技2 小时前
IT6625 HDMI2.0 转双端口 MIPI CSI/DSI 高清转换芯片方案
嵌入式硬件·音视频
NCABGroup3 小时前
从设计与制造入手,如何对PCB的EMC进行优化?
单片机·嵌入式硬件·物联网·pcb·叠层结构·阻抗计算
VBsemi-专注于MOSFET研发定制3 小时前
高端养老陪伴机器人功率链路设计实战:安全、静音与可靠性的融合之道
单片机·嵌入式硬件
汽车芯猿3 小时前
压扁的图像:嵌入式设备中的长方形像素之谜
嵌入式硬件·ui·photoshop
qq_441685753 小时前
CC26xx开发 第二节 GPIO驱动(drivers)开发
嵌入式硬件
hoiii1873 小时前
基于 51 单片机的红外智能垃圾桶源程序
单片机·嵌入式硬件