你的STM32项目安全吗?用芯片唯一ID实现简易防抄板与软件授权(附代码)
2026/4/17 12:01:19 网站建设 项目流程

基于STM32唯一ID构建硬件级软件保护方案实战指南

在嵌入式产品开发中,保护知识产权和防止软件被非法复制是开发者面临的重要挑战。许多初创团队投入大量精力开发的固件,可能因为缺乏基本保护措施而被轻易复制到其他硬件上运行。STM32系列微控制器内置的96位唯一设备标识符(Unique Device ID)为解决这一问题提供了硬件级支持。

1. STM32唯一ID的核心特性与应用价值

STM32的UID是一个出厂时烧录的96位唯一标识符,具有不可修改、全球唯一的特性。根据ST官方文档说明,这个ID的误差率低于10^-15,几乎可以视为绝对唯一标识。与软件生成的序列号不同,UID是芯片物理层面的属性,无法通过常规手段篡改。

典型应用场景包括:

  • 软件授权绑定:将软件与特定硬件设备关联
  • 防抄板保护:防止固件被复制到其他硬件运行
  • 设备身份认证:为物联网设备提供硬件级身份凭证
  • 安全密钥生成:作为加密算法的唯一性种子

注意:不同STM32系列的UID地址可能不同,使用前务必查阅对应型号的参考手册

2. 跨平台UID读取方法与统一接口设计

虽然UID读取原理简单,但不同STM32系列的存储地址存在差异。下面是一个兼容多系列的统一读取实现:

// stm32_uid.h #pragma once #include <stdint.h> #ifdef STM32F1 #define UID_BASE 0x1FFFF7E8 #elif defined(STM32F2) || defined(STM32F4) #define UID_BASE 0x1FFF7A10 #elif defined(STM32F3) #define UID_BASE 0x1FFFF7AC #elif defined(STM32F7) #define UID_BASE 0x1FF0F420 #else #error "Unsupported STM32 series" #endif void read_uid(uint8_t uid[12]);
// stm32_uid.c #include "stm32_uid.h" void read_uid(uint8_t uid[12]) { const uint8_t *p = (const uint8_t *)UID_BASE; for(int i=0; i<12; i++) { uid[i] = *p++; } }

主要STM32系列的UID基地址对比表:

芯片系列UID基地址数据宽度
STM32F10x1FFFF7E896位
STM32F2/F40x1FFF7A1096位
STM32F30x1FFFF7AC96位
STM32F70x1FF0F42096位
STM32H70x1FF1E80096位

3. 基于UID的软件授权方案实现

单纯的UID读取并不能提供有效的保护,需要结合适当的算法将其转化为可验证的授权机制。下面介绍三种实用方案:

3.1 简单校验和方案

// 生成简单的校验和密钥 uint32_t generate_checksum(const uint8_t uid[12]) { uint32_t sum = 0; for(int i=0; i<12; i++) { sum += uid[i]; } return sum ^ 0x55AA55AA; // 加入固定掩码 } // 在软件中验证 bool verify_license() { uint8_t uid[12]; read_uid(uid); uint32_t expected = generate_checksum(uid); uint32_t stored = read_flash_checksum(); // 从Flash读取存储的值 return expected == stored; }

3.2 哈希加密方案

#include "mbedtls/md5.h" // 生成MD5哈希作为设备指纹 void generate_device_fingerprint(const uint8_t uid[12], uint8_t fingerprint[16]) { mbedtls_md5_context ctx; mbedtls_md5_init(&ctx); mbedtls_md5_starts(&ctx); mbedtls_md5_update(&ctx, uid, 12); mbedtls_md5_update(&ctx, (const uint8_t*)"SALT1234", 8); // 加入盐值 mbedtls_md5_finish(&ctx, fingerprint); mbedtls_md5_free(&ctx); }

3.3 激活码机制实现

// 生成基于UID的激活码 void generate_activation_code(const uint8_t uid[12], char code[25]) { uint32_t part1 = *(uint32_t*)uid; uint32_t part2 = *(uint32_t*)(uid+4); uint32_t part3 = *(uint32_t*)(uid+8); part1 = (part1 ^ 0xDEADBEEF) * 0x12345679; part2 = (part2 ^ 0xCAFEBABE) * 0x87654321; part3 = (part3 ^ 0xBAADF00D) * 0x13579BDF; snprintf(code, 25, "%08X-%08X-%08X", part1, part2, part3); } // 验证激活码 bool verify_activation_code(const char* input_code) { uint8_t uid[12]; read_uid(uid); char expected_code[25]; generate_activation_code(uid, expected_code); return strcmp(input_code, expected_code) == 0; }

4. 进阶安全增强策略

基础UID方案虽然有效,但仍有被破解的风险。以下是几种增强安全性的方法:

4.1 动态密钥派生

// 基于UID和运行时信息的动态密钥生成 void generate_dynamic_key(const uint8_t uid[12], uint8_t dynamic_key[16]) { uint32_t tick = HAL_GetTick(); // 获取系统tick值 uint32_t rnd = HAL_GetRandomNumber(); // 获取随机数 mbedtls_md5_context ctx; mbedtls_md5_init(&ctx); mbedtls_md5_starts(&ctx); mbedtls_md5_update(&ctx, uid, 12); mbedtls_md5_update(&ctx, (uint8_t*)&tick, 4); mbedtls_md5_update(&ctx, (uint8_t*)&rnd, 4); mbedtls_md5_finish(&ctx, dynamic_key); mbedtls_md5_free(&ctx); }

4.2 分块验证机制

将验证逻辑分散在代码多处,增加破解难度:

// 在main函数初始化时验证第一部分 bool verify_part1() { uint8_t uid[12]; read_uid(uid); return (uid[0] + uid[5] + uid[11]) == expected_sum1; } // 在关键功能前验证第二部分 bool verify_part2() { uint8_t uid[12]; read_uid(uid); uint32_t hash = uid[3] | (uid[7]<<8) | (uid[9]<<16); return (hash % 17) == expected_remainder; } // 在定时中断中随机验证 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static int counter = 0; if(++counter > 1000) { counter = 0; if(!verify_random_part()) { system_lock(); } } }

4.3 安全存储方案

将关键验证信息存储在芯片的写保护区域或OTP(One-Time Programmable)区域:

// 写入OTP区域示例 void write_otp(uint32_t data) { HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); FLASH_OBProgramInitTypeDef ob; ob.OptionType = OPTIONBYTE_DATA; ob.DATAAddress = 0x1FFF7800; // OTP区域地址 ob.DATAData = data; HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_Lock(); } // 读取OTP区域 uint32_t read_otp() { return *(__IO uint32_t*)0x1FFF7800; }

5. 方案评估与选择建议

不同保护方案在安全性和实现复杂度上各有优劣:

保护方案对比表:

方案类型安全性实现难度适用场景破解难度
简单校验和★★☆★☆☆低价值产品
哈希加密★★★★★☆中等价值产品
激活码机制★★★☆★★★需要分发的商业产品中高
动态密钥★★★★★★★☆高安全性要求产品
分块验证★★★★☆★★★★极高价值产品极高

选择方案时应考虑:

  • 产品的商业价值和复制可能带来的损失
  • 预期的产品生命周期
  • 开发团队的安全技术储备
  • 终端用户的使用体验要求

在实际项目中,我通常会采用组合策略:使用哈希加密作为基础验证,在关键功能处加入动态检查,同时配合OTP存储部分关键信息。这种分层防御的方式能在开发复杂度和安全性之间取得较好平衡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询