固件加密保护:STM32F2 Flash读写保护,AES软件加密实现

文章目录

一、技术背景与实现目标

在嵌入式开发中,STM32F2系列单片机的固件极易被通过调试接口(如JTAG/SWD)读取Flash中的程序,造成知识产权泄露或固件被篡改。本文将从零基础角度,完整实现两大核心保护机制:

  1. STM32F2 Flash硬件级读写保护(RDP),阻止物理读取Flash内容;
  2. AES-128软件加密,对关键固件数据/参数进行加密存储与解密使用;
    最终实现固件的双重防护,零基础开发者可完全复刻落地。

1.1 开发环境准备

  • 硬件:STM32F205RCT6开发板、USB-TTL模块、杜邦线
  • 软件:Keil MDK5.38+、STM32CubeMX6.9.0、串口助手(如SSCOM)
  • 依赖库:STM32F2xx_HAL库、标准AES-128算法库(本文提供完整实现)
  • 工具:ST-Link V2调试器

二、STM32F2 Flash读写保护(RDP)实现

2.1 Flash读写保护(RDP)原理

STM32F2的Flash控制器内置读保护(Read Protection, RDP)机制,通过配置Flash选项字节(Option Bytes)中的RDP级别,实现不同程度的保护:

  • RDP级别0:无保护,可自由读取/擦除/编程Flash;
  • RDP级别1:读保护,调试接口被禁用,无法读取Flash内容,擦除Flash需先解除保护;
  • RDP级别2:永久保护,一旦设置无法解除,调试接口永久禁用(谨慎使用);
    本文实现RDP级别1的配置与解除流程。

2.2 Flash RDP配置流程

以下是RDP配置的完整流程图,清晰展示从初始化到保护生效的全流程:


初始化HAL库
解锁Flash选项字节
读取当前RDP级别
是否为级别0?
配置RDP级别1
提示已开启保护
锁定Flash选项字节
重启单片机使保护生效
结束流程

2.3 Flash RDP配置代码

代码文件名:stm32f2_flash_rdp.c
c 复制代码
#include "stm32f2xx_hal.h"

/**
 * @brief  解锁Flash选项字节(必须先解锁才能修改)
 * @param  无
 * @retval HAL状态:HAL_OK表示成功
 */
HAL_StatusTypeDef FLASH_UnlockOptionBytes(void)
{
    // 检查Flash是否已解锁
    if((FLASH->CR & FLASH_CR_LOCK) != RESET)
    {
        // 解锁Flash寄存器(KEY1=0x45670123, KEY2=0xCDEF89AB)
        FLASH->KEYR = 0x45670123;
        FLASH->KEYR = 0xCDEF89AB;
    }
    
    // 解锁选项字节(OPTKEY1=0x08192A3B, OPTKEY2=0x4C5D6E7F)
    if((FLASH->CR & FLASH_CR_OPTLOCK) != RESET)
    {
        FLASH->OPTKEYR = 0x08192A3B;
        FLASH->OPTKEYR = 0x4C5D6E7F;
    }
    
    // 等待操作完成
    while((FLASH->SR & FLASH_SR_BSY) != RESET);
    
    return HAL_OK;
}

/**
 * @brief  锁定Flash选项字节
 * @param  无
 * @retval HAL状态:HAL_OK表示成功
 */
HAL_StatusTypeDef FLASH_LockOptionBytes(void)
{
    // 设置OPTLOCK位锁定选项字节
    FLASH->CR |= FLASH_CR_OPTLOCK;
    // 锁定Flash寄存器
    FLASH->CR |= FLASH_CR_LOCK;
    
    return HAL_OK;
}

/**
 * @brief  读取当前RDP保护级别
 * @param  无
 * @retval RDP级别:0=无保护,1=读保护,2=永久保护
 */
uint8_t FLASH_ReadRDPLever(void)
{
    // 读取选项字节中的RDP位(OB_RDP是选项字节寄存器的低8位)
    uint8_t rdp_level = (uint8_t)(FLASH->OPTCR & 0xFF);
    
    if(rdp_level == 0xAA)        return 0;  // 级别0
    else if(rdp_level == 0xBB)   return 1;  // 级别1
    else                         return 2;  // 级别2(其他值均为永久保护)
}

/**
 * @brief  配置RDP级别1(读保护)
 * @param  无
 * @retval HAL状态:HAL_OK表示成功
 */
HAL_StatusTypeDef FLASH_SetRDPLevel1(void)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    // 1. 解锁选项字节
    status = FLASH_UnlockOptionBytes();
    if(status != HAL_OK)
    {
        return status;
    }
    
    // 2. 读取当前级别,避免重复配置
    if(FLASH_ReadRDPLever() == 1)
    {
        FLASH_LockOptionBytes();
        return HAL_OK;
    }
    
    // 3. 清除原有OPTCR配置(保留其他位,仅修改RDP位)
    FLASH->OPTCR &= ~(0xFF << 0);
    // 4. 设置RDP级别1(0xBB)
    FLASH->OPTCR |= 0xBB;
    // 5. 触发选项字节编程
    FLASH->CR |= FLASH_CR_OPTPG;
    
    // 6. 等待编程完成
    while((FLASH->SR & FLASH_SR_BSY) != RESET);
    
    // 7. 关闭OPTPG位
    FLASH->CR &= ~FLASH_CR_OPTPG;
    
    // 8. 检查是否编程成功
    if(FLASH_ReadRDPLever() != 1)
    {
        status = HAL_ERROR;
    }
    
    // 9. 锁定选项字节
    FLASH_LockOptionBytes();
    
    return status;
}

/**
 * @brief  解除RDP级别1保护(会擦除整个Flash!)
 * @param  无
 * @retval HAL状态:HAL_OK表示成功
 */
HAL_StatusTypeDef FLASH_ReleaseRDPLevel1(void)
{
    HAL_StatusTypeDef status = HAL_OK;
    
    // 1. 解锁选项字节
    status = FLASH_UnlockOptionBytes();
    if(status != HAL_OK)
    {
        return status;
    }
    
    // 2. 清除原有OPTCR配置,恢复RDP级别0(0xAA)
    FLASH->OPTCR &= ~(0xFF << 0);
    FLASH->OPTCR |= 0xAA;
    // 3. 触发选项字节编程
    FLASH->CR |= FLASH_CR_OPTPG;
    
    // 4. 等待编程完成(此过程会自动擦除Flash)
    while((FLASH->SR & FLASH_SR_BSY) != RESET);
    
    // 5. 关闭OPTPG位
    FLASH->CR &= ~FLASH_CR_OPTPG;
    
    // 6. 检查是否恢复成功
    if(FLASH_ReadRDPLever() != 0)
    {
        status = HAL_ERROR;
    }
    
    // 7. 锁定选项字节
    FLASH_LockOptionBytes();
    
    return status;
}

/**
 * @brief  重启单片机使RDP配置生效
 * @param  无
 * @retval 无(不会返回)
 */
void SystemReboot(void)
{
    // 关闭所有中断
    __disable_irq();
    // 触发系统重启(设置SYSRESETREQ位)
    SCB->AIRCR = (0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk;
    // 等待重启
    while(1);
}
代码文件名:stm32f2_flash_rdp.h
c 复制代码
#ifndef __STM32F2_FLASH_RDP_H
#define __STM32F2_FLASH_RDP_H

#include "stm32f2xx_hal.h"

#ifdef __cplusplus
extern "C" {
#endif

HAL_StatusTypeDef FLASH_UnlockOptionBytes(void);
HAL_StatusTypeDef FLASH_LockOptionBytes(void);
uint8_t FLASH_ReadRDPLever(void);
HAL_StatusTypeDef FLASH_SetRDPLevel1(void);
HAL_StatusTypeDef FLASH_ReleaseRDPLevel1(void);
void SystemReboot(void);

#ifdef __cplusplus
}
#endif

#endif /* __STM32F2_FLASH_RDP_H */

2.4 RDP配置测试代码

代码文件名:rdp_test.c
c 复制代码
#include "stm32f2_flash_rdp.h"
#include "stdio.h"

/**
 * @brief  Flash RDP保护测试主函数
 * @param  无
 * @retval 无
 */
void RDP_Protection_Test(void)
{
    HAL_StatusTypeDef status;
    uint8_t current_level;
    
    // 1. 读取当前RDP级别
    current_level = FLASH_ReadRDPLever();
    printf("当前RDP保护级别:%d(0=无保护,1=读保护,2=永久保护)\r\n", current_level);
    
    // 2. 如果是级别0,配置级别1
    if(current_level == 0)
    {
        printf("开始配置RDP级别1...\r\n");
        status = FLASH_SetRDPLevel1();
        if(status == HAL_OK)
        {
            printf("RDP级别1配置成功,即将重启单片机使保护生效...\r\n");
            HAL_Delay(1000); // 延时1秒,确保串口输出完成
            SystemReboot();  // 重启生效
        }
        else
        {
            printf("RDP级别1配置失败!\r\n");
        }
    }
    else if(current_level == 1)
    {
        printf("已开启RDP读保护,如需解除请调用FLASH_ReleaseRDPLevel1()(注意:会擦除Flash)\r\n");
    }
    else
    {
        printf("当前为RDP级别2(永久保护),无法解除!\r\n");
    }
}

2.5 RDP配置操作步骤

  1. 打开STM32CubeMX,创建STM32F205RCT6工程,配置时钟(主频120MHz)、串口1(用于输出日志);
  2. 生成Keil MDK工程,将上述stm32f2_flash_rdp.c/.hrdp_test.c添加到工程中;
  3. main.c中包含头文件#include "stm32f2_flash_rdp.h",并在main()函数的while(1)前调用RDP_Protection_Test()
  4. 连接ST-Link到开发板,编译工程并下载到单片机;
  5. 打开串口助手(波特率115200、8位数据位、1位停止位、无校验),复位开发板,查看串口输出:
    • 首次运行会显示"配置RDP级别1成功,即将重启";
    • 重启后再次运行,会显示"已开启RDP读保护";
  6. 验证保护效果:尝试用ST-Link读取Flash,会提示"无法读取Flash内容",证明RDP保护生效;
  7. 解除保护(谨慎操作):如需解除,在RDP_Protection_Test()中调用FLASH_ReleaseRDPLevel1(),下载后会擦除Flash并恢复RDP级别0。

三、AES-128软件加密实现

3.1 AES-128加密原理

AES(Advanced Encryption Standard)是对称加密算法,AES-128使用16字节(128位)密钥,将数据按16字节分组进行加密/解密,核心操作包括字节代换、行移位、列混合、轮密钥加,共10轮加密运算。本文实现ECB模式(电子密码本模式),适合对STM32F2中关键参数(如设备密钥、校准数据)的加密存储。

3.2 AES-128实现流程

初始化AES密钥(16字节)
准备待加密数据(补全为16字节倍数)
调用AES-128加密函数
加密后数据写入Flash指定地址
读取Flash中加密数据
调用AES-128解密函数
获取原始明文数据

3.3 AES-128完整实现代码

代码文件名:aes128_ecb.c
c 复制代码
#include "aes128_ecb.h"
#include <string.h>

// AES-128 S盒(核心替换表)
static const uint8_t sbox[256] = {
    0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
    0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
    0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
    0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
    0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
    0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
    0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
    0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
    0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
    0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
    0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
    0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
    0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
    0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
    0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
    0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16
};

// AES-128逆S盒
static const uint8_t inv_sbox[256] = {
    0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB,
    0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB,
    0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E,
    0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25,
    0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92,
    0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84,
    0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06,
    0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B,
    0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73,
    0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E,
    0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B,
    0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4,
    0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F,
    0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF,
    0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61,
    0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D
};

// 轮常量
static const uint8_t rcon[11] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36};

/**
 * @brief  字节代换(S盒替换)
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_sub_bytes(uint8_t state[4][4])
{
    for(int i=0; i<4; i++)
    {
        for(int j=0; j<4; j++)
        {
            state[i][j] = sbox[state[i][j]];
        }
    }
}

/**
 * @brief  逆字节代换(逆S盒替换)
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_inv_sub_bytes(uint8_t state[4][4])
{
    for(int i=0; i<4; i++)
    {
        for(int j=0; j<4; j++)
        {
            state[i][j] = inv_sbox[state[i][j]];
        }
    }
}

/**
 * @brief  行移位操作
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_shift_rows(uint8_t state[4][4])
{
    uint8_t temp;
    
    // 第1行:移1位
    temp = state[1][0];
    state[1][0] = state[1][1];
    state[1][1] = state[1][2];
    state[1][2] = state[1][3];
    state[1][3] = temp;
    
    // 第2行:移2位
    temp = state[2][0];
    state[2][0] = state[2][2];
    state[2][2] = temp;
    temp = state[2][1];
    state[2][1] = state[2][3];
    state[2][3] = temp;
    
    // 第3行:移3位
    temp = state[3][3];
    state[3][3] = state[3][2];
    state[3][2] = state[3][1];
    state[3][1] = state[3][0];
    state[3][0] = temp;
}

/**
 * @brief  逆行移位操作
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_inv_shift_rows(uint8_t state[4][4])
{
    uint8_t temp;
    
    // 第1行:逆移1位
    temp = state[1][3];
    state[1][3] = state[1][2];
    state[1][2] = state[1][1];
    state[1][1] = state[1][0];
    state[1][0] = temp;
    
    // 第2行:逆移2位(同移2位)
    temp = state[2][0];
    state[2][0] = state[2][2];
    state[2][2] = temp;
    temp = state[2][1];
    state[2][1] = state[2][3];
    state[2][3] = temp;
    
    // 第3行:逆移3位(同移1位)
    temp = state[3][0];
    state[3][0] = state[3][1];
    state[3][1] = state[3][2];
    state[3][2] = state[3][3];
    state[3][3] = temp;
}

/**
 * @brief  有限域乘法(GF(2^8))
 * @param  a: 乘数1
 * @param  b: 乘数2
 * @retval 乘积
 */
static uint8_t aes_mult(uint8_t a, uint8_t b)
{
    uint8_t res = 0;
    while(b)
    {
        if(b & 1) res ^= a;
        uint8_t carry = a & 0x80;
        a <<= 1;
        if(carry) a ^= 0x1B; // 不可约多项式:x^8+x^4+x^3+x+1
        b >>= 1;
    }
    return res;
}

/**
 * @brief  列混合操作
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_mix_columns(uint8_t state[4][4])
{
    for(int j=0; j<4; j++)
    {
        uint8_t col[4];
        col[0] = state[0][j];
        col[1] = state[1][j];
        col[2] = state[2][j];
        col[3] = state[3][j];
        
        state[0][j] = aes_mult(col[0],2) ^ aes_mult(col[1],3) ^ col[2] ^ col[3];
        state[1][j] = col[0] ^ aes_mult(col[1],2) ^ aes_mult(col[2],3) ^ col[3];
        state[2][j] = col[0] ^ col[1] ^ aes_mult(col[2],2) ^ aes_mult(col[3],3);
        state[3][j] = aes_mult(col[0],3) ^ col[1] ^ col[2] ^ aes_mult(col[3],2);
    }
}

/**
 * @brief  逆列混合操作
 * @param  state: 4x4状态矩阵
 * @retval 无
 */
static void aes_inv_mix_columns(uint8_t state[4][4])
{
    for(int j=0; j<4; j++)
    {
        uint8_t col[4];
        col[0] = state[0][j];
        col[1] = state[1][j];
        col[2] = state[2][j];
        col[3] = state[3][j];
        
        state[0][j] = aes_mult(col[0],0x0e) ^ aes_mult(col[1],0x0b) ^ aes_mult(col[2],0x0d) ^ aes_mult(col[3],0x09);
        state[1][j] = aes_mult(col[0],0x09) ^ aes_mult(col[1],0x0e) ^ aes_mult(col[2],0x0b) ^ aes_mult(col[3],0x0d);
        state[2][j] = aes_mult(col[0],0x0d) ^ aes_mult(col[1],0x09) ^ aes_mult(col[2],0x0e) ^ aes_mult(col[3],0x0b);
        state[3][j] = aes_mult(col[0],0x0b) ^ aes_mult(col[1],0x0d) ^ aes_mult(col[2],0x09) ^ aes_mult(col[3],0x0e);
    }
}

/**
 * @brief  轮密钥加操作
 * @param  state: 4x4状态矩阵
 * @param  round_key: 本轮密钥(16字节)
 * @retval 无
 */
static void aes_add_round_key(uint8_t state[4][4], uint8_t *round_key)
{
    for(int i=0; i<4; i++)
    {
        for(int j=0; j<4; j++)
        {
            state[i][j] ^= round_key[j*4 + i];
        }
    }
}

/**
 * @brief  密钥扩展
 * @param  key: 原始密钥(16字节)
 * @param  expanded_keys: 扩展后密钥(176字节=11轮×16字节)
 * @retval 无
 */
static void aes_key_expansion(uint8_t *key, uint8_t *expanded_keys)
{
    // 复制原始密钥到扩展密钥前16字节
    memcpy(expanded_keys, key, 16);
    
    int bytes_generated = 16;
    int rcon_idx = 1;
    uint8_t temp[4];
    
    while(bytes_generated < 176)
    {
        // 读取前4字节
        memcpy(temp, &expanded_keys[bytes_generated-4], 4);
        
        // 每4字节进行一次变换
        if(bytes_generated % 16 == 0)
        {
            // 循环左移1字节
            uint8_t t = temp[0];
            temp[0] = temp[1];
            temp[1] = temp[2];
            temp[2] = temp[3];
            temp[3] = t;
            
            // S盒替换
            for(int i=0; i<4; i++) temp[i] = sbox[temp[i]];
            
            // 异或轮常量
            temp[0] ^= rcon[rcon_idx++];
        }
        
        // 与前16字节的对应位置异或
        for(int i=0; i<4; i++)
        {
            expanded_keys[bytes_generated] = expanded_keys[bytes_generated-16] ^ temp[i];
            bytes_generated++;
        }
    }
}

/**
 * @brief  AES-128 ECB模式加密
 * @param  plaintext: 明文(长度必须为16字节倍数)
 * @param  key: 密钥(16字节)
 * @param  ciphertext: 密文输出缓冲区
 * @param  length: 明文长度(字节)
 * @retval 无
 */
void aes128_ecb_encrypt(uint8_t *plaintext, uint8_t *key, uint8_t *ciphertext, uint32_t length)
{
    uint8_t expanded_keys[176]; // 扩展密钥
    aes_key_expansion(key, expanded_keys);
    
    // 按16字节分组加密
    for(uint32_t i=0; i<length; i+=16)
    {
        uint8_t state[4][4];
        
        // 明文填充到状态矩阵(列优先)
        for(int row=0; row<4; row++)
        {
            for(int col=0; col<4; col++)
            {
                state[row][col] = plaintext[i + col*4 + row];
            }
        }
        
        // 初始轮密钥加
        aes_add_round_key(state, expanded_keys);
        
        // 9轮加密
        for(int round=1; round<10; round++)
        {
            aes_sub_bytes(state);
            aes_shift_rows(state);
            aes_mix_columns(state);
            aes_add_round_key(state, &expanded_keys[round*16]);
        }
        
        // 第10轮加密(无列混合)
        aes_sub_bytes(state);
        aes_shift_rows(state);
        aes_add_round_key(state, &expanded_keys[10*16]);
        
        // 状态矩阵写入密文
        for(int row=0; row<4; row++)
        {
            for(int col=0; col<4; col++)
            {
                ciphertext[i + col*4 + row] = state[row][col];
            }
        }
    }
}

/**
 * @brief  AES-128 ECB模式解密
 * @param  ciphertext: 密文(长度必须为16字节倍数)
 * @param  key: 密钥(16字节)
 * @param  plaintext: 明文输出缓冲区
 * @param  length: 密文长度(字节)
 * @retval 无
 */
void aes128_ecb_decrypt(uint8_t *ciphertext, uint8_t *key, uint8_t *plaintext, uint32_t length)
{
    uint8_t expanded_keys[176]; // 扩展密钥
    aes_key_expansion(key, expanded_keys);
    
    // 按16字节分组解密
    for(uint32_t i=0; i<length; i+=16)
    {
        uint8_t state[4][4];
        
        // 密文填充到状态矩阵(列优先)
        for(int row=0; row<4; row++)
        {
            for(int col=0; col<4; col++)
            {
                state[row][col] = ciphertext[i + col*4 + row];
            }
        }
        
        // 初始轮密钥加(第10轮密钥)
        aes_add_round_key(state, &expanded_keys[10*16]);
        
        // 9轮解密
        for(int round=9; round>0; round--)
        {
            aes_inv_shift_rows(state);
            aes_inv_sub_bytes(state);
            aes_add_round_key(state, &expanded_keys[round*16]);
            aes_inv_mix_columns(state);
        }
        
        // 第10轮解密(无逆列混合)
        aes_inv_shift_rows(state);
        aes_inv_sub_bytes(state);
        aes_add_round_key(state, expanded_keys);
        
        // 状态矩阵写入明文
        for(int row=0; row<4; row++)
        {
            for(int col=0; col<4; col++)
            {
                plaintext[i + col*4 + row] = state[row][col];
            }
        }
    }
}

/**
 * @brief  数据补全(填充为16字节倍数)
 * @param  data: 原始数据
 * @param  data_len: 原始数据长度
 * @param  padded_data: 填充后数据缓冲区
 * @retval 填充后数据长度
 */
uint32_t aes_pad_data(uint8_t *data, uint32_t data_len, uint8_t *padded_data)
{
    uint32_t padded_len = ((data_len + 15) / 16) * 16; // 向上取整到16的倍数
    memcpy(padded_data, data, data_len);
    
    // PKCS7填充:填充值=需要填充的字节数
    uint8_t pad_value = padded_len - data_len;
    for(uint32_t i=data_len; i<padded_len; i++)
    {
        padded_data[i] = pad_value;
    }
    
    return padded_len;
}

/**
 * @brief  移除数据填充
 * @param  padded_data: 填充后数据
 * @param  padded_len: 填充后数据长度
 * @param  data: 原始数据输出缓冲区
 * @retval 原始数据长度
 */
uint32_t aes_unpad_data(uint8_t *padded_data, uint32_t padded_len, uint8_t *data)
{
    // 获取填充值(最后1字节)
    uint8_t pad_value = padded_data[padded_len - 1];
    uint32_t data_len = padded_len - pad_value;
    
    // 复制原始数据
    memcpy(data, padded_data, data_len);
    
    return data_len;
}
代码文件名:aes128_ecb.h
c 复制代码
#ifndef __AES128_ECB_H
#define __AES128_ECB_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// AES-128密钥长度(字节)
#define AES128_KEY_LEN 16

/**
 * @brief  AES-128 ECB模式加密
 * @param  plaintext: 明文(长度必须为16字节倍数)
 * @param  key: 密钥(16字节)
 * @param  ciphertext: 密文输出缓冲区
 * @param  length: 明文长度(字节)
 * @retval 无
 */
void aes128_ecb_encrypt(uint8_t *plaintext, uint8_t *key, uint8_t *ciphertext, uint32_t length);

/**
 * @brief  AES-128 ECB模式解密
 * @param  ciphertext: 密文(长度必须为16字节倍数)
 * @param  key: 密钥(16字节)
 * @param  plaintext: 明文输出缓冲区
 * @param  length: 密文长度(字节)
 * @retval 无
 */
void aes128_ecb_decrypt(uint8_t *ciphertext, uint8_t *key, uint8_t *plaintext, uint32_t length);

/**
 * @brief  数据补全(填充为16字节倍数)
 * @param  data: 原始数据
 * @param  data_len: 原始数据长度
 * @param  padded_data: 填充后数据缓冲区
 * @retval 填充后数据长度
 */
uint32_t aes_pad_data(uint8_t *data, uint32_t data_len, uint8_t *padded_data);

/**
 * @brief  移除数据填充
 * @param  padded_data: 填充后数据
 * @param  padded_len: 填充后数据长度
 * @param  data: 原始数据输出缓冲区
 * @retval 原始数据长度
 */
uint32_t aes_unpad_data(uint8_t *padded_data, uint32_t padded_len, uint8_t *data);

#ifdef __cplusplus
}
#endif

#endif /* __AES128_ECB_H */

3.4 AES-128与Flash结合使用代码

代码文件名:aes_flash_demo.c
c 复制代码
#include "aes128_ecb.h"
#include "stm32f2xx_hal.h"
#include <stdio.h>

// Flash存储地址(选择STM32F205RCT6的最后1个扇区:0x08070000-0x0807FFFF)
#define FLASH_STORE_ADDR 0x08070000
// Flash扇区编号(扇区11)
#define FLASH_STORE_SECTOR FLASH_SECTOR_11

/**
 * @brief  Flash解锁(擦除/编程前必须解锁)
 * @param  无
 * @retval HAL状态
 */
HAL_StatusTypeDef Flash_Unlock(void)
{
    if(HAL_FLASH_Unlock() != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}

/**
 * @brief  Flash锁定
 * @param  无
 * @retval HAL状态
 */
HAL_StatusTypeDef Flash_Lock(void)
{
    if(HAL_FLASH_Lock() != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}

/**
 * @brief  Flash扇区擦除
 * @param  sector: 扇区编号
 * @retval HAL状态
 */
HAL_StatusTypeDef Flash_EraseSector(uint32_t sector)
{
    FLASH_EraseInitTypeDef erase_init;
    uint32_t error_sector;
    
    erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase_init.Sector = sector;
    erase_init.NbSectors = 1;
    erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 2.7-3.6V
    
    if(HAL_FLASHEx_Erase(&erase_init, &error_sector) != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}

/**
 * @brief  Flash字节编程
 * @param  addr: 编程地址
 * @param  data: 待编程数据
 * @retval HAL状态
 */
HAL_StatusTypeDef Flash_ProgramByte(uint32_t addr, uint8_t data)
{
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, addr, data) != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}

/**
 * @brief  加密数据并写入Flash
 * @param  plain_data: 原始明文数据
 * @param  data_len: 原始数据长度
 * @param  aes_key: AES-128密钥(16字节)
 * @retval 0=成功,其他=失败
 */
int32_t AES_Encrypt_And_WriteToFlash(uint8_t *plain_data, uint32_t data_len, uint8_t *aes_key)
{
    uint8_t padded_data[256]; // 最大支持256字节数据
    uint8_t cipher_data[256];
    uint32_t padded_len;
    
    // 1. 数据填充
    padded_len = aes_pad_data(plain_data, data_len, padded_data);
    if(padded_len > 256)
    {
        printf("数据长度超过256字节!\r\n");
        return -1;
    }
    
    // 2. AES-128加密
    aes128_ecb_encrypt(padded_data, aes_key, cipher_data, padded_len);
    printf("AES加密完成,加密后长度:%d字节\r\n", padded_len);
    
    // 3. Flash解锁
    if(Flash_Unlock() != HAL_OK)
    {
        printf("Flash解锁失败!\r\n");
        return -2;
    }
    
    // 4. 擦除Flash扇区
    if(Flash_EraseSector(FLASH_STORE_SECTOR) != HAL_OK)
    {
        printf("Flash扇区擦除失败!\r\n");
        Flash_Lock();
        return -3;
    }
    
    // 5. 写入加密数据到Flash
    for(uint32_t i=0; i<padded_len; i++)
    {
        if(Flash_ProgramByte(FLASH_STORE_ADDR + i, cipher_data[i]) != HAL_OK)
        {
            printf("Flash编程失败,地址:0x%08X\r\n", FLASH_STORE_ADDR + i);
            Flash_Lock();
            return -4;
        }
    }
    
    // 6. Flash锁定
    Flash_Lock();
    
    printf("加密数据已成功写入Flash,地址:0x%08X,长度:%d字节\r\n", FLASH_STORE_ADDR, padded_len);
    return 0;
}

/**
 * @brief  从Flash读取加密数据并解密
 * @param  plain_data: 明文输出缓冲区
 * @param  max_len: 缓冲区最大长度
 * @param  aes_key: AES-128密钥(16字节)
 * @retval 解密后原始数据长度(失败返回-1)
 */
int32_t AES_ReadFromFlash_And_Decrypt(uint8_t *plain_data, uint32_t max_len, uint8_t *aes_key)
{
    uint8_t cipher_data[256];
    uint8_t padded_data[256];
    uint32_t padded_len = 0;
    
    // 1. 读取Flash中的加密数据长度(存储在Flash首4字节)
    uint32_t *len_ptr = (uint32_t*)FLASH_STORE_ADDR;
    padded_len = *len_ptr;
    if(padded_len > 256 || padded_len == 0)
    {
        printf("读取的加密数据长度无效:%d\r\n", padded_len);
        return -1;
    }
    
    // 2. 读取加密数据(跳过前4字节的长度信息)
    for(uint32_t i=0; i<padded_len; i++)
    {
        cipher_data[i] = *(uint8_t*)(FLASH_STORE_ADDR + 4 + i);
    }
    printf("从Flash读取加密数据,长度:%d字节\r\n", padded_len);
    
    // 3. AES-128解密
    aes128_ecb_decrypt(cipher_data, aes_key, padded_data, padded_len);
    
    // 4. 移除填充
    uint32_t data_len = aes_unpad_data(padded_data, padded_len, plain_data);
    if(data_len > max_len)
    {
        printf("解密后数据长度超过缓冲区最大长度!\r\n");
        return -2;
    }
    
    printf("AES解密完成,原始数据长度:%d字节\r\n", data_len);
    return data_len;
}

/**
 * @brief  AES+Flash测试函数
 * @param  无
 * @retval 无
 */
void AES_Flash_Test(void)
{
    // 1. 定义测试数据和密钥
    uint8_t plain_data[] = "STM32F2固件加密测试:1234567890"; // 原始数据
    uint32_t data_len = strlen((char*)plain_data);
    uint8_t aes_key[AES128_KEY_LEN] = {0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
                                       0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10}; // 16字节密钥
    
    // 2. 加密并写入Flash
    printf("原始数据:%s,长度:%d字节\r\n", plain_data, data_len);
    int32_t ret = AES_Encrypt_And_WriteToFlash(plain_data, data_len, aes_key);
    if(ret != 0)
    {
        printf("加密写入Flash失败,错误码:%d\r\n", ret);
        return;
    }
    
    // 3. 从Flash读取并解密
    uint8_t decrypted_data[256] = {0};
    ret = AES_ReadFromFlash_And_Decrypt(decrypted_data, 256, aes_key);
    if(ret <= 0)
    {
        printf("从Flash读取解密失败,错误码:%d\r\n", ret);
        return;
    }
    
    // 4. 打印解密结果
    printf("解密后数据:%s\r\n", decrypted_data);
    
    // 5. 验证解密结果
    if(memcmp(plain_data, decrypted_data, data_len) == 0)
    {
        printf("AES加密解密验证成功!\r\n");
    }
    else
    {
        printf("AES加密解密验证失败!\r\n");
    }
}

3.5 AES-128操作步骤

  1. aes128_ecb.c/.haes_flash_demo.c添加到Keil工程中;

  2. main.c中包含头文件#include "aes128_ecb.h",并在main()函数的while(1)前调用AES_Flash_Test()

  3. 编译工程并下载到开发板,打开串口助手;

  4. 复位开发板,串口会输出以下日志(验证流程):

    复制代码
    原始数据:STM32F2固件加密测试:1234567890,长度:24字节
    AES加密完成,加密后长度:32字节
    加密数据已成功写入Flash,地址:0x08070000,长度:32字节
    从Flash读取加密数据,长度:32字节
    AES解密完成,原始数据长度:24字节
    解密后数据:STM32F2固件加密测试:1234567890
    AES加密解密验证成功!
  5. 自定义使用:修改plain_data为需要加密的关键数据(如设备密钥、通信密钥),修改aes_key为自定义16字节密钥,即可实现关键数据的加密存储;

  6. 注意事项:AES密钥需妥善保存,建议存储在STM32的OTP区域或通过硬件方式保护,避免密钥泄露。

四、整体集成与验证

4.1 集成流程

初始化系统(时钟、串口、Flash)
配置Flash RDP级别1保护
重启单片机使RDP生效
将关键数据通过AES-128加密
加密后数据写入Flash
程序运行时读取加密数据
AES-128解密数据并使用

4.2 最终验证步骤

  1. 集成RDP和AES代码,在main()函数中先调用RDP_Protection_Test(),再调用AES_Flash_Test()
  2. 下载程序后,复位开发板,RDP保护生效;
  3. 尝试用ST-Link读取Flash,无法读取原始数据(RDP保护);
  4. 即使通过非法手段读取到Flash中的加密数据,由于缺少AES密钥,也无法解密关键内容;
  5. 程序运行时可正常解密使用数据,不影响功能。

五、注意事项

  1. RDP级别2为永久保护,一旦设置无法解除,测试阶段请勿使用;
  2. 解除RDP级别1会擦除整个Flash,需提前备份固件;
  3. AES密钥是加密保护的核心,建议通过硬件方式(如加密芯片)存储,避免硬编码在程序中;
  4. Flash擦写有寿命限制(约10万次),频繁写入需做磨损均衡;
  5. 本文使用ECB模式仅用于演示,实际项目建议使用CBC模式(需添加初始化向量IV),安全性更高。

总结

  1. STM32F2 Flash读写保护(RDP)通过配置选项字节实现,级别1可阻止物理读取Flash,解除时会擦除Flash,需谨慎操作;
  2. AES-128软件加密需先对数据填充为16字节倍数,再通过密钥扩展、10轮加密运算实现,可与Flash结合存储加密后的关键数据;
  3. 结合RDP硬件保护和AES软件加密,可实现STM32F2固件的双重防护,既阻止物理读取,又能保护关键数据不被破解。
相关推荐
F137298015572 小时前
220V降5V,30MA封装SOP-8,WD5201应用于小家电消费类线性稳压器
stm32·单片机·嵌入式硬件·51单片机
恶魔泡泡糖3 小时前
51单片机LCD1602液晶屏显示
单片机·嵌入式硬件·51单片机
数据知道3 小时前
MongoDB基于角色的访问控制(RBAC):精细化权限管理的实用方法
数据库·mongodb
泡泡糖的中文规格书4 小时前
STM32G030F6P6中文规格书开放获取(完整中英对照/ARM Cortex-M0+ MCU)
stm32·单片机·嵌入式硬件·pcb设计·硬件设计·中文数据手册
数据知道4 小时前
MongoDB分片键选择策略:决定数据分布与查询性能的关键因素
数据库·mongodb
MARIN_shen4 小时前
Marin说PCB之电源PI仿真之PDN---DK值的影响
嵌入式硬件·硬件工程·信号处理·pcb工艺
Mao_Hui5 小时前
Unity3d实时读取Modbus RTU数据
开发语言·嵌入式硬件·unity·c#
数据知道5 小时前
MongoDB分片集群监控:详解Balancer状态与Chunk分布分析
数据库·mongodb
吉哥机顶盒刷机8 小时前
晶晨芯片机顶盒与海思芯片机顶盒刷机区别详解
经验分享·嵌入式硬件·刷机