stm32 AES256加密 串口IAP升级 bootloader程序 通过上位机将keil生成的BIN文件进行AES加密,得到新的加密文件,加密需要自己设置秘钥,加密升级包直接烧录不能运行。 通过串口升级上位机将加密包发送到单片机, 单片机接收到数据后,会根据你事先设置好的秘钥,对数据进行还原,再写入。 解密完成,程序升级成功。 购买本资料可以获得: 带有AES解密功能的bootloader程序 串口升级的上位机软件 AES加密上位机软件 说明文档一份 如需上位机源码 单独指出默认没有 理论上,只要移植AES的.c和.h文件,并且你能将数据发送到单片机串口,就能用任意方式来对单片机进行升级,包括但不限于wifi,蓝牙,4G模块等。
搞嵌入式最刺激的瞬间,莫过于冒着变砖风险给设备远程升级固件。今天咱们整点硬核的——给STM32套上AES256加密的盔甲玩IAP升级。别被专业名词吓到,说白了就是让单片机自己给自己做外科手术换程序,还得全程防偷窥。
先看实战效果:你的bin文件经过上位机AES加密后,直接烧进芯片会变砖(故意的)。只有通过专用串口工具发送加密包,芯片用预设密钥现场解密,才能成功升级。整个过程就像给程序穿了件隐身衣,没密钥的人连升级包长啥样都看不见。
上点干货,bootloader里最带劲的解密环节代码:
// AES上下文结构体,藏着密钥信息 struct aes_context ctx; uint8_t ciphertext[16]; // 密文缓冲区 uint8_t plaintext[16]; // 明文输出 void AES_DecryptChunk(uint8_t* input) { aes_setkey_dec(&ctx, secret_key, 256); // 反着用密钥 aes_crypt_ecb(&ctx, AES_DECRYPT, input, plaintext); // 解密后立即开溜,别让数据在内存里裸奔 Flash_Write(APP_ADDRESS + offset, plaintext, 16); offset += 16; }注意看aessetkeydec这个调用,这里藏着个骚操作——加密解密共用同一套密钥,但需要明确指定解密模式。就像同一把钥匙开锁和上锁方向不同,搞反了直接变乱码。
stm32 AES256加密 串口IAP升级 bootloader程序 通过上位机将keil生成的BIN文件进行AES加密,得到新的加密文件,加密需要自己设置秘钥,加密升级包直接烧录不能运行。 通过串口升级上位机将加密包发送到单片机, 单片机接收到数据后,会根据你事先设置好的秘钥,对数据进行还原,再写入。 解密完成,程序升级成功。 购买本资料可以获得: 带有AES解密功能的bootloader程序 串口升级的上位机软件 AES加密上位机软件 说明文档一份 如需上位机源码 单独指出默认没有 理论上,只要移植AES的.c和.h文件,并且你能将数据发送到单片机串口,就能用任意方式来对单片机进行升级,包括但不限于wifi,蓝牙,4G模块等。
串口接收也别傻等,上DMA+环形缓冲区才够专业:
#define BUFFER_SIZE 1024 __align(4) uint8_t dma_buffer[BUFFER_SIZE]; // 内存对齐,DMA不认野指针 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){ USART_ClearITPendingBit(USART1, USART_IT_IDLE); DMA_Cmd(DMA1_Channel5, DISABLE); uint16_t recv_len = BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); ringbuf_put(&rx_buf, dma_buffer, recv_len); // 塞进环形队列 DMA_SetCurrDataCounter(DMA1_Channel5, BUFFER_SIZE); DMA_Cmd(DMA1_Channel5, ENABLE); } }这里IDLE中断+DMA的组合拳,专治各种数据流抽搐。环形缓冲区就像个传送带,边收边处理,完全不用担心数据洪流冲垮系统。
解密完成后最心跳的瞬间——跳转新程序:
typedef void (*pFunction)(void); pFunction Jump_To_Application; void JumpToApp(void) { if(((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000) == 0x20000000){ Jump_To_Application = (pFunction)*(__IO uint32_t*)(APP_ADDRESS + 4); __set_MSP(*(__IO uint32_t*)APP_ADDRESS); Jump_To_Application(); } else { // 赶紧闪红灯报警 } }这个强制类型转换堪称C语言的黑暗魔法,直接把地址转成函数指针。检查栈顶地址是否在RAM范围内是个重要安全措施,防止跳进火坑。
上位机那边也别闲着,加密时记得加个自定义文件头:
header = struct.pack('<II32s', 0xA5A5A5A5, file_size, md5.digest()) cipher = AES.new(key, AES.MODE_ECB) with open('encrypted.bin', 'wb') as f: f.write(header) for chunk in read_chunks(orig_bin): f.write(cipher.encrypt(pad(chunk)))这个0xA5A5A5A5魔数相当于接头暗号,bootloader先校验这个再干活。ECB模式虽然不如CBC安全,但在单片机端实现简单,对资源紧张的环境更友好。
最后提醒几个坑点:
- 中断向量表偏移记得改,别让程序跑飞了
- 加密前的bin文件最好做CRC或MD5校验
- 密钥千万别裸奔在代码里,至少做点异或混淆
- 升级失败要有回滚机制,别真变砖
这套方案最妙的是通信层与bootloader解耦,今天用串口,明天换WiFi模块照样能用。曾经有个项目用这方案通过4G网络给沙漠里的气象站升级,看着日志里滚动的解密成功提示,比玩扫雷通关还刺激。