2. 基于AES-128-CBC的固件加密存储与解密执行
任务目标
本案例实现一个基于AES-128-CBC的固件加密存储与解密执行方案:固件镜像以密文形式存放于外部Flash,MCU上电后通过Bootloader将密文读取到RAM,解密后跳转执行,防止攻击者通过读取外部Flash直接提取固件明文。
方案架构
- 存储布局:外部Flash 0x0000~0x0FFF存放AES密钥(16字节)与IV(16字节),0x1000起存放密文固件。
- 执行流程:Bootloader(内部Flash)→ 读取密钥/IV → 从外部Flash读取密文 → AES-128-CBC解密到RAM → 跳转至RAM执行。
- 安全边界:密钥仅存在于内部Flash与MCU运行时RAM,外部Flash始终只有密文。
硬件依赖
- MCU:STM32F407(Cortex-M4,内部Flash 1MB,RAM 192KB)
- 外部Flash:W25Q64(SPI接口,8MB)
- 密码库:mbedTLS 2.28(仅使用AES-128-CBC部分)
关键完整代码
// ========== crypto_boot.h ==========
#ifndef CRYPTO_BOOT_H
#define CRYPTO_BOOT_H
#include <stdint.h>
#define AES_KEY_SIZE 16
#define AES_BLOCK_SIZE 16
#define FIRMWARE_MAX_SIZE (64 * 1024) // 64KB
#define FLASH_KEY_ADDR 0x0000
#define FLASH_FW_ADDR 0x1000
// 外部Flash驱动接口(由硬件层提供)
void ext_flash_read(uint32_t addr, uint8_t *buf, uint32_t len);
// 解密并跳转
void boot_decrypt_and_run(void);
#endif
// ========== crypto_boot.c ==========
#include "crypto_boot.h"
#include "mbedtls/aes.h"
#include <string.h>
// 内部Flash中存储的密钥(编译时写入,实际产品可配合OTP)
static const uint8_t aes_key[AES_KEY_SIZE] = {
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6,
0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C
};
// 应用程序入口地址(RAM中解密后的跳转目标)
#define APP_ENTRY_ADDR 0x20010000
void boot_decrypt_and_run(void) {
uint8_t iv[AES_BLOCK_SIZE];
uint8_t ciphertext[FIRMWARE_MAX_SIZE];
uint8_t plaintext[FIRMWARE_MAX_SIZE];
uint32_t fw_size;
mbedtls_aes_context aes_ctx;
// 1. 读取IV(外部Flash头部)
ext_flash_read(FLASH_KEY_ADDR + AES_KEY_SIZE, iv, AES_BLOCK_SIZE);
// 2. 读取密文固件大小(密文前4字节存储明文长度)
uint8_t size_buf[4];
ext_flash_read(FLASH_FW_ADDR, size_buf, 4);
fw_size = (size_buf[0] << 24) | (size_buf[1] << 16) |
(size_buf[2] << 8) | size_buf[3];
// 3. 读取密文(跳过4字节长度头)
ext_flash_read(FLASH_FW_ADDR + 4, ciphertext, fw_size);
// 4. AES-128-CBC解密
mbedtls_aes_init(&aes_ctx);
mbedtls_aes_setkey_dec(&aes_ctx, aes_key, 128);
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT,
fw_size, iv, ciphertext, plaintext);
mbedtls_aes_free(&aes_ctx);
// 5. 跳转到RAM中的应用程序
void (*app_entry)(void) = (void (*)(void))(APP_ENTRY_ADDR);
memcpy((void *)APP_ENTRY_ADDR, plaintext, fw_size);
__DSB();
app_entry();
}
// ========== 固件加密工具(上位机,Python) ==========
/*
用于生成外部Flash烧录文件:encrypt_fw.py
输入:app.bin(明文固件)
输出:fw_encrypted.bin(含4字节长度头 + 密文)
*/
上位机加密脚本(encrypt_fw.py)
#!/usr/bin/env python3
from Crypto.Cipher import AES
import struct, sys
KEY = bytes([0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6,
0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C])
IV = bytes([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F])
def pad(data):
pad_len = 16 - (len(data) % 16)
return data + bytes([pad_len] * pad_len)
def encrypt_fw(input_file, output_file):
with open(input_file, 'rb') as f:
fw = f.read()
fw_padded = pad(fw)
cipher = AES.new(KEY, AES.MODE_CBC, IV)
encrypted = cipher.encrypt(fw_padded)
with open(output_file, 'wb') as f:
f.write(struct.pack('>I', len(fw))) # 明文长度
f.write(encrypted)
print(f"Encrypted {len(fw)} bytes -> {len(encrypted)+4} bytes")
if __name__ == '__main__':
encrypt_fw(sys.argv[1], sys.argv[2])
运行与烧录步骤
- 编译Bootloader:将crypto_boot.c加入工程,链接时确保Bootloader位于内部Flash起始地址(0x08000000)。
- 生成密文固件:
python3 encrypt_fw.py app.bin fw_encrypted.bin - 烧录外部Flash:将fw_encrypted.bin烧录至W25Q64的0x1000偏移处;将IV(16字节)烧录至0x0010偏移处。
- 烧录Bootloader:通过SWD将Bootloader烧入MCU内部Flash。
- 上电执行:复位后Bootloader自动完成解密与跳转。
验证方法与判定标准
- 功能验证:在app.bin中让LED以1Hz闪烁。若上电后LED正常闪烁,说明解密执行链路完整。
- 安全验证:使用SPI Flash编程器直接读取W25Q64 0x1000处数据,搜索app.bin中的明文字符串(如固件版本号),若无法找到,证明固件以密文存储。
- 边界测试:故意篡改外部Flash中任一密文字节,重新上电,观察程序不应正常运行(解密失败导致崩溃或看门狗复位),证明密文完整性校验生效。
常见问题
- RAM不足:若固件超过64KB,需改用DMA分段解密或增大RAM;本案例适用于中小型固件。
- 对齐要求:mbedTLS的CBC解密要求输入长度为16的倍数,上位机脚本已做PKCS7填充处理。
- 密钥保护:实际产品应将密钥存于MCU内部OTP或使用MPU配置内部Flash为不可读,防止通过调试接口提取。