文章目录
-
- 一、技术背景与实现目标
-
- [1.1 开发环境准备](#1.1 开发环境准备)
- [二、STM32F2 Flash读写保护(RDP)实现](#二、STM32F2 Flash读写保护(RDP)实现)
-
- [2.1 Flash读写保护(RDP)原理](#2.1 Flash读写保护(RDP)原理)
- [2.2 Flash RDP配置流程](#2.2 Flash RDP配置流程)
- [2.3 Flash RDP配置代码](#2.3 Flash RDP配置代码)
- [2.4 RDP配置测试代码](#2.4 RDP配置测试代码)
- [2.5 RDP配置操作步骤](#2.5 RDP配置操作步骤)
- 三、AES-128软件加密实现
-
- [3.1 AES-128加密原理](#3.1 AES-128加密原理)
- [3.2 AES-128实现流程](#3.2 AES-128实现流程)
- [3.3 AES-128完整实现代码](#3.3 AES-128完整实现代码)
- [3.4 AES-128与Flash结合使用代码](#3.4 AES-128与Flash结合使用代码)
- [3.5 AES-128操作步骤](#3.5 AES-128操作步骤)
- 四、整体集成与验证
-
- [4.1 集成流程](#4.1 集成流程)
- [4.2 最终验证步骤](#4.2 最终验证步骤)
- 五、注意事项
一、技术背景与实现目标
在嵌入式开发中,STM32F2系列单片机的固件极易被通过调试接口(如JTAG/SWD)读取Flash中的程序,造成知识产权泄露或固件被篡改。本文将从零基础角度,完整实现两大核心保护机制:
- STM32F2 Flash硬件级读写保护(RDP),阻止物理读取Flash内容;
- 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配置操作步骤
- 打开STM32CubeMX,创建STM32F205RCT6工程,配置时钟(主频120MHz)、串口1(用于输出日志);
- 生成Keil MDK工程,将上述
stm32f2_flash_rdp.c/.h、rdp_test.c添加到工程中; - 在
main.c中包含头文件#include "stm32f2_flash_rdp.h",并在main()函数的while(1)前调用RDP_Protection_Test(); - 连接ST-Link到开发板,编译工程并下载到单片机;
- 打开串口助手(波特率115200、8位数据位、1位停止位、无校验),复位开发板,查看串口输出:
- 首次运行会显示"配置RDP级别1成功,即将重启";
- 重启后再次运行,会显示"已开启RDP读保护";
- 验证保护效果:尝试用ST-Link读取Flash,会提示"无法读取Flash内容",证明RDP保护生效;
- 解除保护(谨慎操作):如需解除,在
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操作步骤
-
将
aes128_ecb.c/.h、aes_flash_demo.c添加到Keil工程中; -
在
main.c中包含头文件#include "aes128_ecb.h",并在main()函数的while(1)前调用AES_Flash_Test(); -
编译工程并下载到开发板,打开串口助手;
-
复位开发板,串口会输出以下日志(验证流程):
原始数据:STM32F2固件加密测试:1234567890,长度:24字节 AES加密完成,加密后长度:32字节 加密数据已成功写入Flash,地址:0x08070000,长度:32字节 从Flash读取加密数据,长度:32字节 AES解密完成,原始数据长度:24字节 解密后数据:STM32F2固件加密测试:1234567890 AES加密解密验证成功! -
自定义使用:修改
plain_data为需要加密的关键数据(如设备密钥、通信密钥),修改aes_key为自定义16字节密钥,即可实现关键数据的加密存储; -
注意事项:AES密钥需妥善保存,建议存储在STM32的OTP区域或通过硬件方式保护,避免密钥泄露。
四、整体集成与验证
4.1 集成流程
初始化系统(时钟、串口、Flash)
配置Flash RDP级别1保护
重启单片机使RDP生效
将关键数据通过AES-128加密
加密后数据写入Flash
程序运行时读取加密数据
AES-128解密数据并使用
4.2 最终验证步骤
- 集成RDP和AES代码,在
main()函数中先调用RDP_Protection_Test(),再调用AES_Flash_Test(); - 下载程序后,复位开发板,RDP保护生效;
- 尝试用ST-Link读取Flash,无法读取原始数据(RDP保护);
- 即使通过非法手段读取到Flash中的加密数据,由于缺少AES密钥,也无法解密关键内容;
- 程序运行时可正常解密使用数据,不影响功能。
五、注意事项
- RDP级别2为永久保护,一旦设置无法解除,测试阶段请勿使用;
- 解除RDP级别1会擦除整个Flash,需提前备份固件;
- AES密钥是加密保护的核心,建议通过硬件方式(如加密芯片)存储,避免硬编码在程序中;
- Flash擦写有寿命限制(约10万次),频繁写入需做磨损均衡;
- 本文使用ECB模式仅用于演示,实际项目建议使用CBC模式(需添加初始化向量IV),安全性更高。
总结
- STM32F2 Flash读写保护(RDP)通过配置选项字节实现,级别1可阻止物理读取Flash,解除时会擦除Flash,需谨慎操作;
- AES-128软件加密需先对数据填充为16字节倍数,再通过密钥扩展、10轮加密运算实现,可与Flash结合存储加密后的关键数据;
- 结合RDP硬件保护和AES软件加密,可实现STM32F2固件的双重防护,既阻止物理读取,又能保护关键数据不被破解。