MCU程序加密保护(二)ID 验证法 加密与解密_stm32 uid加密
STM32 微控制器内部具有一个 96 位全球唯一的 CPU ID,不可更改。开发者可利用此 ID 实现芯片绑定和程序加密,增强软件安全性。ID 验证法就是利用这个 UID,对每颗芯片的身份进行识别和绑定,从而防止程序被复制。
实现方式:
-
在程序首次运行或出厂时,读取 CPU ID,经过不可逆算法(如 CRC32、SHA256)处理后生成一个校验值;
-
将该校验值写入 Flash;
-
每次程序启动时重新计算当前芯片 ID 的校验值,并与 Flash 中存储的值进行比较;
-
如果匹配,程序正常运行;否则程序终止,防止非法拷贝。
三、这种方法的优点:
下面只讲一种方法,方法肯定是不唯一的!
#define UID_BASE_ADDR 0x1FFFF7E8U#define FLASH_CRC_ADDRESS 0x0800FC00U // 最后一页(64KB Flash)起始地址#define UID_WORD_COUNT 3#define FLASH_PAGE_SIZE 1024U#define CRC32_POLYNOMIAL 0xEDB88320Uint fputc(int ch, FILE *f){ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch;}void send_string(const char* str){ HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), HAL_MAX_DELAY);}void Print_UID(void){ const uint32_t* uid = (const uint32_t*)UID_BASE_ADDR; char buf[64]; snprintf(buf, sizeof(buf), \"[STM32 UID] %08lX-%08lX-%08lX\\r\\n\", uid[2], uid[1], uid[0]); send_string(buf);}uint32_t Calculate_UID_CRC32(void){ const uint32_t *uid = (const uint32_t *)UID_BASE_ADDR; uint32_t crc = 0xFFFFFFFF; for (int i = 0; i < UID_WORD_COUNT; i++) { crc ^= uid[i]; for (int j = 0; j > 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; } } return crc ^ 0xFFFFFFFF;}uint32_t Read_Stored_CRC(void){ return *(const uint32_t *)FLASH_CRC_ADDRESS;}void Verify_UID(void){ uint32_t calc_crc = Calculate_UID_CRC32(); uint32_t stored_crc = Read_Stored_CRC(); if (calc_crc != stored_crc) { char buf[128]; snprintf(buf, sizeof(buf), \"[ERROR] UID CRC Mismatch! Calc: 0x%08lX, Stored: 0x%08lX\\r\\n\", calc_crc, stored_crc); send_string(buf); Error_Handler(); } else { send_string(\"[INFO] UID CRC Verified OK\\r\\n\"); }}void Store_UID_CRC_To_Flash(void){ uint32_t crc = Calculate_UID_CRC32(); HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef EraseInitStruct = { .TypeErase = FLASH_TYPEERASE_PAGES, .PageAddress = FLASH_CRC_ADDRESS, .NbPages = 1 }; uint32_t PageError = 0; if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK) { send_string(\"[ERROR] Flash Erase Failed\\r\\n\"); HAL_FLASH_Lock(); return; } if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_CRC_ADDRESS, crc) != HAL_OK) { send_string(\"[ERROR] Flash Program Failed\\r\\n\"); HAL_FLASH_Lock(); return; } HAL_FLASH_Lock(); send_string(\"[INFO] UID CRC Stored to Flash\\r\\n\");}void Auto_Bind_UID_CRC_If_Needed(void){ uint32_t stored_crc = Read_Stored_CRC(); if (stored_crc == 0xFFFFFFFF) // 判断是否首次上电 { send_string(\"[INFO] No UID CRC Stored. Binding current chip...\\r\\n\"); Store_UID_CRC_To_Flash(); // 写入当前芯片 UID 的 CRC }}void SystemClock_Config(void);int main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); Auto_Bind_UID_CRC_If_Needed(); Print_UID(); Verify_UID(); while (1) { 。。。。。。}}
防止程序在“非原芯片”上运行
uint32_t calc_crc = Calculate_UID_CRC32();
uint32_t stored_crc = Read_Stored_CRC();
对 MCU 内置唯一的 UID 地址 0x1FFFF7E8
做了 CRC 校验,并将第一次计算的结果写入 Flash:
Store_UID_CRC_To_Flash(); // 用于首次存储 CRC
校验通过才能继续运行,否则 Error_Handler()
阻止执行。
这个机制可有效阻止程序被别人拷贝到其他 STM32 芯片后运行。
其次0xEDB88320U这个是干嘛的?
0xEDB88320U
是 CRC32 多项式的反转形式(Reflected Polynomial),它是你代码中用于计算 CRC32 校验值的核心常数。
什么是 CRC?
CRC(循环冗余校验)是一种常用于数据完整性校验的哈希算法,在嵌入式系统、通信、存储等地方被广泛使用。
它通过一个多项式除法运算对数据生成一个固定长度的校验值,用于检测数据在传输或存储中是否被篡改或损坏。
你用到的 0xEDB88320U
是什么?
这是标准 CRC32 算法中使用的 反射形式多项式:
标准 CRC32 多项式(正向):
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1x^{32} + x^{26} + x^{23} + x^{22} + x^{16} + x^{12} + x^{11} + x^{10} + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
对应的二进制多项式值是:0x04C11DB7
反转版本(Reflected CRC):就是你代码中使用的
0xEDB88320\\text{0xEDB88320}0xEDB88320
它等价于对 0x04C11DB7
的位反转处理。
STM32 的 HAL_CRC
或很多嵌入式代码实现中,都是按位反转(LSB first)方式计算 CRC,因此使用 0xEDB88320
是标准做法。
这种反转方式效率高,和实际硬件或工业协议(比如 Ethernet、ZIP 文件、PNG 图片、STM32 Bootloader 校验)保持一致。
此外上述代码已经验证,拷贝后在别的扳机程序无法运行!达到效果!
但是!加密完总得自己解密哈哈,相比于闪存加密的方法,这个方法还有很多漏洞的,毕竟是软件加密,如下为漏洞分析!
此为量产方式
当然漏洞是有的,但是我们将闪存保护法跟id验证法放在一起,并且将下述方法加在一起,更多方法进行融合,达到真正的加密效果!
目前只是对 UID 本身做 CRC32,攻击者拿到程序和 CRC 算法很容易在自己芯片上重算并替换。
改进: 在计算 CRC 前,把 UID 跟一个“固化私钥”一起混合,再做 CRC。
#define SECRET_KEY_WORD 0xA5A5BEEFULuint32_t Calculate_Mixed_CRC32(void){ const uint32_t *uid = (const uint32_t*)UID_BASE_ADDR; uint32_t buffer[UID_WORD_COUNT + 1] = { uid[0], uid[1], uid[2], SECRET_KEY_WORD }; uint32_t crc = 0xFFFFFFFF; for (int i = 0; i < UID_WORD_COUNT + 1; i++) { crc ^= buffer[i]; for (int j = 0; j > 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; } } return crc ^ 0xFFFFFFFF;}
结合 Option Bytes 自动锁定
在 Auto_Bind_UID_CRC_If_Needed()
中一旦绑定完成,即可顺带触发 RDP Level1 和写保护:
void Secure_Lock_Device(void){ FLASH_OBProgramInitTypeDef ob = {0}; HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); HAL_FLASHEx_OBGetConfig(&ob); // 只在 Level0 时生效,避免重复擦除 if (ob.RDPLevel == OB_RDP_LEVEL_0) { ob.OptionType = OPTIONBYTE_RDP | OPTIONBYTE_WRP; ob.RDPLevel = OB_RDP_LEVEL_1; ob.WRPState = OB_WRPSTATE_ENABLE; ob.WRPPage = OB_WRP_PAGES60TO63; // 保护 CRC 存储页 HAL_FLASHEx_OBProgram(&ob); } HAL_FLASH_OB_Lock(); HAL_FLASH_Lock();}
使用硬件 CRC 外设或查表加速
软件 bitwise CRC32 性能偏低,可考虑:
STM32F1 的 CRC 外设(部分型号支持)
或者用 256 项的查表法,把 0xEDB88320
的查表数组预先生成,运算速度提升 5~10 倍。
static const uint32_t crc32_table[256] = { /* 预生成的 256 项 */ };uint32_t CRC32_Table(const uint8_t *data, uint32_t len) { uint32_t crc = 0xFFFFFFFF; while (len--) { crc = (crc >> 8) ^ crc32_table[(crc ^ *data++) & 0xFF]; } return crc ^ 0xFFFFFFFF;}