# 单片机固件解密实现 - 文件迁移说明 ## 概述 本文档说明如何将BootLoader中的AES解密和固件下载功能移植到其他STM32工程。 --- ## 一、必需文件清单 ### 1. AES加密/解密核心模块 **文件位置:** `BootLoaderL051/decadriver/` | 文件名 | 说明 | 必需 | |--------|------|------| | `aes.c` | AES-256加密解密核心实现 | ✓ | | `aes.h` | AES模块头文件 | ✓ | **功能说明:** - 实现AES-256-CBC加密解密算法 - 使用硬编码密钥 `kTable`(32字节) - 主要函数: - `aesDecInit()` - 解密初始化 - `aesDecrypt(buffer, chainBlock)` - 解密16字节数据块 - `aesEncInit()` - 加密初始化 - `aesEncrypt(buffer, chainBlock)` - 加密16字节数据块 **密钥(kTable):** ```c const unsigned char kTable[32] = { 0x21, 0x05, 0x04, 0x19, 0x89, 0x12, 0x13, 0x00, 0x18, 0xDE, 0xCA, 0x01, 0x30, 0x52, 0x01, 0x23, 0x13, 0x05, 0x33, 0x19, 0x93, 0x07, 0x08, 0x00, 0x20, 0x5B, 0xA3, 0x4A, 0x21, 0xBA, 0xF9, 0xFC }; ``` --- ### 2. Ymodem协议接收模块 **文件位置:** `BootLoaderL051/decadriver/` | 文件名 | 说明 | 必需 | |--------|------|------| | `ymodem.c` | Ymodem协议实现(含解密逻辑) | ✓ | | `ymodem.h` | Ymodem协议头文件 | ✓ | **功能说明:** - 通过串口接收加密的固件文件 - 自动调用AES解密(第335-341行) - 数据包CRC16校验 - 写入Flash存储 **核心流程:** ```c // 在 Ymodem_Receive() 函数中(第330-360行) 1. 接收1024字节数据包 2. 对每16字节进行AES-CBC解密: for (j = 0; j < packet_length; j += 16) { aesDecrypt(BufferIn, bufferOut); BufferIn += 16; } 3. 写入Flash ``` **⚠️ 注意:目前缺少固件校验和验证!** --- ### 3. Flash操作模块 **文件位置:** `BootLoaderL051/HAL/` | 文件名 | 说明 | 必需 | |--------|------|------| | `Flash.c` | Flash擦除、读写操作 | ✓ | | `Flash.h` | Flash模块头文件定义 | ✓ | **功能说明:** - Flash擦除、读、写操作 - 地址和大小配置 - 写入验证 **主要函数:** - `FLASH_Prepare(Address, Len)` - 擦除Flash - `FLASH_Write(Address, pData, Len)` - 写入数据 - `FLASH_Read(Address, Readbuff, Len)` - 读取数据 - `FLASH_If_Write()` - Ymodem专用写入接口 **需要修改的配置:** ```c // 在 Flash.h 中根据实际Flash大小修改 #define USER_FLASH_END_ADDRESS 0x0800FBFF // Flash结束地址 #define APPLICATION_ADDRESS 0x8005000 // APP起始地址 #define PAGE_SIZE 0x100 // 页大小 ``` --- ### 4. 下载管理模块 **文件位置:** `BootLoaderL051/decadriver/` | 文件名 | 说明 | 必需 | |--------|------|------| | `menu.c` | 固件下载主流程 | ✓ | | `menu.h` | 下载管理头文件 | ✓ | **功能说明:** - 调用Ymodem接收固件 - 下载完成后跳转到应用程序 - 错误处理 **主要函数:** - `SerialDownload()` - 串口固件下载 - `Main_Menu()` - 主菜单循环 --- ### 5. 串口通信模块(需自行适配) **文件位置:** `BootLoaderL051/HAL/` | 文件名 | 说明 | 必需 | |--------|------|------| | `Usart.c` | 串口收发实现 | ✓ | | `Usart.h` | 串口头文件 | ✓ | **需要提供的接口函数:** ```c // 在你的工程中需要实现这些函数 uint32_t SerialKeyPressed(uint8_t *key); // 检测串口是否有数据 void SerialPutChar(uint8_t c); // 发送一个字符 void SerialPutString(uint8_t *s); // 发送字符串 ``` --- ## 二、依赖关系图 ``` main.c └─> menu.c (下载管理) └─> ymodem.c (协议+解密) ├─> aes.c (解密算法) ├─> Flash.c (Flash操作) └─> Usart.c (串口通信) ``` --- ## 三、移植步骤 ### Step 1: 复制核心文件 将以下文件复制到新工程: ``` 新工程/ ├── AES/ │ ├── aes.c │ └── aes.h ├── Bootloader/ │ ├── ymodem.c │ ├── ymodem.h │ ├── menu.c │ └── menu.h └── HAL/ ├── Flash.c ├── Flash.h ├── Usart.c (或自行实现) └── Usart.h ``` ### Step 2: 修改配置参数 1. **Flash.h** - 根据芯片型号修改: - `USER_FLASH_END_ADDRESS` - Flash结束地址 - `APPLICATION_ADDRESS` - 应用程序起始地址 - `PAGE_SIZE` - Flash页大小 2. **aes.c** - 确认密钥(通常不需要改): - `kTable[32]` - 确保与上位机一致 ### Step 3: 实现串口接口 在你的串口模块中实现: ```c uint32_t SerialKeyPressed(uint8_t *key) { // 检查串口是否收到数据,如果有则读取到*key // 返回1表示有数据,0表示无数据 } void SerialPutChar(uint8_t c) { // 发送一个字符 HAL_UART_Transmit(&huart1, &c, 1, 100); } void SerialPutString(uint8_t *s) { // 发送字符串 HAL_UART_Transmit(&huart1, s, strlen((char*)s), 1000); } ``` ### Step 4: 添加到编译系统 - 将所有.c文件添加到编译器 - 将头文件路径添加到include path - 确保HAL库和CMSIS已正确配置 ### Step 5: 在main中调用 ```c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 检查是否需要进入BootLoader if (/* 检测条件,如按键或标志位 */) { Main_Menu(); // 进入固件下载 } // 否则跳转到应用程序 IAP_JumpTo(APPLICATION_ADDRESS); while(1); } ``` --- ## 四、当前已知问题 ⚠️ ### 1. **缺少固件校验和验证** - 上位机在固件末尾添加了4字节校验和(小端序) - 单片机接收后**没有验证**此校验和 - **风险:** 固件损坏也可能被执行 **需要添加的代码位置:** `menu.c` 的 `SerialDownload()` 函数完成后 **建议实现:** ```c // 在 SerialDownload() 成功后,跳转前添加 uint32_t VerifyFirmwareChecksum(uint32_t address, uint32_t size) { uint32_t i; uint32_t calculated_sum = 0; uint32_t stored_sum; uint8_t *firmware = (uint8_t *)address; // 计算固件数据的校验和(不包括最后4字节) for (i = 0; i < size - 4; i++) { calculated_sum += firmware[i]; } calculated_sum &= 0xFFFFFFFF; // 读取存储的校验和(小端序) stored_sum = firmware[size - 4] | (firmware[size - 3] << 8) | (firmware[size - 2] << 16) | (firmware[size - 1] << 24); return (calculated_sum == stored_sum) ? 1 : 0; } ``` ### 2. **C#程序的密钥未被使用** - C#界面显示的密钥输入框实际无效 - 真正的密钥硬编码在aes.c的kTable中 - 如需更改密钥,需要同时修改C#的MYT.dll和单片机的aes.c --- ## 五、测试验证 ### 加密固件格式验证 已通过Python程序验证: - ✓ 使用AES-256-CBC,IV为全0 - ✓ 密钥为kTable(32字节) - ✓ 校验和正确添加在固件末尾 - ✓ 解密后数据与原始固件一致 **测试命令:** ```bash python verify_encryption.py ``` --- ## 六、技术细节 ### AES-256-CBC 加密流程 ``` 1. 原始固件数据(HEX转BIN) 2. 计算32位累加和(校验和) 3. 追加4字节校验和(小端序) 4. 填充至16字节倍数(0xFF填充) 5. AES-256-CBC加密(IV=全0) 6. 保存为.Bin文件 ``` ### 解密流程(单片机端) ``` 1. 通过Ymodem接收加密数据包 2. 每16字节进行AES-CBC解密 3. 写入Flash 4. (缺失)验证校验和 5. 跳转到应用程序 ``` --- ## 七、联系与支持 如有问题,请检查: 1. Flash地址配置是否正确 2. 串口波特率和配置 3. 密钥是否一致 4. HAL库版本兼容性 **建议优先实现固件校验和验证功能,以提高系统可靠性!** --- **文档创建日期:** 2025-12-20 **适用于:** STM32L051及类似型号 **工具链:** Keil MDK / STM32CubeIDE