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])

运行与烧录步骤

  1. 编译Bootloader:将crypto_boot.c加入工程,链接时确保Bootloader位于内部Flash起始地址(0x08000000)。
  2. 生成密文固件:python3 encrypt_fw.py app.bin fw_encrypted.bin
  3. 烧录外部Flash:将fw_encrypted.bin烧录至W25Q64的0x1000偏移处;将IV(16字节)烧录至0x0010偏移处。
  4. 烧录Bootloader:通过SWD将Bootloader烧入MCU内部Flash。
  5. 上电执行:复位后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为不可读,防止通过调试接口提取。