1113 字
6 分钟
STM32 软件实现 AES 加解密:原理、实现与优化

1. 为什么选择软件实现 AES?#

AES(Advanced Encryption Standard,高级加密标准)是目前应用最广泛的对称加密算法。虽然许多中高端 STM32 芯片(如 STM32F4/L4 系列的某些型号)带有硬件 CRYP 加速器,但在以下场景软件实现更具优势:

  • 芯片兼容性:适用于不带硬件加密模块的低成本型号(如 STM32F1/F0 系列)。
  • 代码可移植性:方便在不同架构(如 RISC-V 或 AVR)之间复用。
  • 自定义安全性:便于实现非标准长度的密钥或魔改特定轮次。

2. AES 加解密核心流程#

AES 处理的基本单位是 128 位(16 字节) 的数据块。根据密钥长度(128/192/256 位),算法会执行 10 到 14 轮不等的处理。

核心操作步骤:#

  1. 密钥扩展 (Key Expansion):将初始密钥扩展为多个轮密钥。
  2. 字节替换 (SubBytes):通过 S-Box(置换盒)实现非线性映射,防止差分攻击。
  3. 行移位 (ShiftRows):将状态矩阵的行进行循环移位。
  4. 列混合 (MixColumns):通过有限域乘法对列进行混淆。
  5. 轮密钥加 (AddRoundKey):将当前状态与对应的轮密钥进行异或。

3. 工程代码实现#

头文件 (aes.h)#

定义支持的加密模式和接口。在嵌入式中,推荐使用 CBC 模式,因为它比 ECB 模式安全性更高。

#ifndef _AES_H
#define _AES_H
#include <stdint.h>
#define AES_KEY_LENGTH 128 // 支持 128, 192, 256 位
#define AES_MODE_CBC 1 // 密码分组链接模式(推荐)
// 初始化:密钥扩展
void aes_init(const uint8_t *pKey);
// 加密:输入数据长度必须是16字节的整数倍
void aes_encrypt(const uint8_t *pPlainText, uint8_t *pCipherText, uint32_t nDataLen, const uint8_t *pIV);
// 解密
void aes_decrypt(const uint8_t *pCipherText, uint8_t *pPlainText, uint32_t nDataLen, const uint8_t *pIV);
#endif

核心源文件 (aes.c)#

为了保证执行效率,S-Box 通常以查找表的形式存在。

#include "aes.h"
#include <string.h>
// 常量定义
#define Nb 4 // 状态矩阵的列数
#if AES_KEY_LENGTH == 128
#define Nk 4
#define Nr 10
#elif AES_KEY_LENGTH == 192
#define Nk 6
#define Nr 12
#else
#define Nk 8
#define Nr 14
#endif
// 静态查找表:S-Box (节省空间建议将其放入 Flash)
static const uint8_t sBox[256] = { /* ... 0x63, 0x7c, 0x77 ... */ };
static const uint8_t invSBox[256] = { /* ... 0x52, 0x09, 0x6a ... */ };
// 存储扩展后的轮密钥
static uint8_t g_roundKeyTable[4 * Nb * (Nr + 1)];
// 核心函数:密钥扩展
void aes_init(const uint8_t *pKey) {
// 实现详见 AES 标准算法库,将初始密钥映射至 g_roundKeyTable
}
// 分组加密逻辑 (CBC 模式)
void aes_encrypt(const uint8_t *pPlainText, uint8_t *pCipherText, uint32_t nDataLen, const uint8_t *pIV) {
uint8_t tempIV[16];
memcpy(tempIV, pIV, 16);
for (uint32_t i = 0; i < nDataLen; i += 16) {
// CBC 模式:加密前先与前一组密文(或IV)异或
for (int j = 0; j < 16; j++) {
pCipherText[i + j] = pPlainText[i + j] ^ tempIV[j];
}
// 调用单个 Block 的加密函数
block_encrypt(&pCipherText[i]);
// 更新 IV 为当前密文
memcpy(tempIV, &pCipherText[i], 16);
}
}

4. 针对 STM32 的性能优化建议#

在嵌入式开发中,资源非常宝贵。以下是几个实用的优化方向:

  • Flash vs RAM: S-Box 和 Rcon 等常量数组应使用 const 关键字修饰,确保它们存储在 Flash 空间而非消耗昂贵的 RAM。
  • T-Table 优化: 如果 Flash 空间充裕,可以使用预计算的 T-Table(将 SubBytes、ShiftRows、MixColumns 合并为 4 个查找表),这能将加密速度提升 3-5 倍。
  • 字对齐访问: STM32 是 32 位架构。在异或(XOR)操作时,将 uint8_t 指针转换为 uint32_t 指针进行批量异或,可以显著减少指令周期。
  • 循环展开 (Loop Unrolling): 对于 Nr 轮的循环,适当展开内层循环可以减少跳转指令的开销,这在 F0/F1 等低频芯片上效果明显。

5. 快速上手示例#

int main(void) {
// 准备 16 字节对齐的数据
uint8_t key[16] = "stm32_aes_key_v1";
uint8_t iv[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
uint8_t data[16] = "Hello STM32 AES";
uint8_t cipher[16];
uint8_t plain[16];
// 初始化算法
aes_init(key);
// 执行加密
aes_encrypt(data, cipher, 16, iv);
// 执行解密
aes_decrypt(cipher, plain, 16, iv);
// 通过串口打印输出对比结果...
while(1);
}

6. 结论#

软件实现 AES 虽然在吞吐量上不如硬件加速器,但其极高的灵活性使其成为嵌入式开发者的必备“工具箱”成员。通过合理的查找表和字对齐优化,在 STM32F103 (72MHz) 上加解密一张小型的传感器数据帧仅需几百微秒,完全满足大部分物联网(IoT)安全通信的需求。

STM32 软件实现 AES 加解密:原理、实现与优化
https://hw.rscclub.website/posts/stm32rjaesss/
作者
杨月昌
发布于
2018-06-16
许可协议
CC BY-NC-SA 4.0