news 2026/9/9 20:15:27

STM32F407 AES加解密实战:集成tiny-AES-c与三种模式测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F407 AES加解密实战:集成tiny-AES-c与三种模式测试

简介:面向STM32F4系列嵌入式开发者,这份AES加密解密测试程序以C语言工程形式提供了完整的加解密验证方案。程序基于STM32F4 HAL库实现,重点演示了PKCS7填充与解填充算法,确保数据块满足AES要求的128位长度;同时通过串口1(PB6/PB7)接收不定长数据,解决了异步通信中数据长度不固定的处理难题,非常适合需要在工业控制、物联网节点等场景中快速实现数据加密的工程师参考。压缩包内共775个文件,以368个C源文件、142个H头文件和70个汇编文件为主体,辅以ICF链接脚本、UVprojx工程文件及AXF、HEX烧录文件,涵盖从源码阅读到编译下载的完整链路,整体包体约21.3MB。目前已有257人学习下载,对于希望掌握STM32F4平台AES应用和串口不定长接收技巧的开发者而言,这份工程具有直接的借鉴与复用价值。 先把话说在前面:网上聊STM32加密的资料不少,但大多数停在“看别人跑通”的阶段。我这次是真刀真枪在STM32F407上做了一个AES加密解密测试程序,从Keil5建工程开始,到tiny-AES-c集成,再到ECB、CBC、CTR三种模式逐个验证,中间踩过的坑比想象中多。这篇东西就是完整的试验记录,适合正在给MCU加加密功能的嵌入式工程师参考,也适合刚接触AES、想在F4上快速落地的朋友抄作业。我会把工程搭建、核心代码、测试方法、性能测试和故障排查全写出来,保证你照着能跑通。

1. 项目目标与方案选型背后的逻辑

1.1 这个测试程序到底要验证什么

很多人以为“测试程序”就是调通一次加解密就结束了,但我这次的目标远不止于此。拿到一个加密库,你要确认几件事:第一,算法实现在这块芯片上是不是正确的,必须用标准测试向量去比对,而不是只看“能加密也能解密”这种自我安慰的结果;第二,不同分组模式的行为是否符合预期,比如CBC模式里上一个密文块会影响下一个明文块,这个用打印日志能直观看到;第三,性能基准要摸清楚,后续做协议设计时才能估算每秒能处理多少包数据;第四,最好留一套通用性强的调试接口,以后每个项目都能复用这个测试框架。

我把这些目标写成了四条验收标准:能用NIST官方测试向量跑出完全一致的密文、能连续加密解密多组数据不出错、能测出加密和解密各自耗时、串口日志能清晰显示每个中间结果。这样测试程序才不是一次性玩具,而是能沉淀成通用工具的东西。

1.2 为什么用软件AES而不是依赖硬件加密

选型时很多人第一反应是“F4不是有硬件AES吗”。这里要澄清一个容易混淆的点:STM32F4系列只有少数型号内置了硬件加密处理器CRYP(比如STM32F437、STM32F439),常用的STM32F405、F407、F411、F427等型号并没有CRYP外设。我手里这块F407就属于没有硬件AES的型号,所以软实现是唯一选择,除非换芯片。

软件AES虽然不如硬件加速器快,但有几个实打实的优点:第一,纯C代码可以在任何F4型号上编译运行,代码可移植性极强;第二,不依赖复杂的寄存器配置和DMA通道,出问题好排查;第三,模式切换非常灵活,ECB、CBC、CTR可以随意切换,这在硬件加密引擎上反而麻烦得多。另外,有些F4型号内置了TRNG真随机数发生器,这个好东西可以配合AES生成随机IV或盐,测试程序里我顺手把它也用上了。

2. 开发环境与Keil5工程搭建

2.1 Keil5添加STM32F4器件支持的正确姿势

如果你用的是新装的MDK5,打开工程前第一件事是确认Device Pack装好了。选芯片时找不到STM32F407IGHx这类型号,十有八九是DFP包没装。我习惯在Pack Installer里直接搜索STM32F4,然后安装STMicroelectronics出品的STM32F4xx_DFP;也可以去ST官网下载Pack文件后双击安装,效果一样。

如果项目是用STM32CubeMX生成的MDK工程,这个包一般会自动带上,不用手动折腾。我这边因为要给旧工程加功能,所以选择手动添加。踩过一次坑是:MDK5打开旧工程后提示找不到器件,但Pack Installer里明明显示装了,最后发现是版本不兼容。解决方法是把旧版本DFP卸载,装和MDK5版本匹配的新包,然后再重新选一次Device型号。所以别急着写代码,先让编译器能识别芯片,否则后面全白搭。

2.2 加密库选型:tiny-AES-c与mbedTLS怎么选

MCU上做AES加密,市面上最常用的两个软件库是tiny-AES-c和mbedTLS。我做了个对比表,方便你按场景取舍:

对比项tiny-AES-cmbedTLS
代码体积极小,核心就两个文件较大,含完整密码套件
第三方依赖无,直接编译需要裁剪配置,依赖传送层
支持的模式AES128/192/256的ECB、CBC、CTR完整AES且支持TLS/哈希/证书等
适用场景裸机MCU快速验证、单一加密需求需要TLS握手、MQTT加密通信等重场景
调试难度简单,代码一眼能看完较复杂,配置项多

我的建议是:测试程序阶段先上tiny-AES-c。理由很实在:代码量小,出问题能直接单步看算法细节;而如果你只是在做设备传感器数据加密,这个库完全够用。等到后面真的需要上TLS,再迁移到mbedTLS也不迟,因为AES部分已经被验证过,迁移成本低。

2.3 快速集成加密源文件

从GitHub拉取kokke/tiny-AES-c这个仓库,把aes.caes.h放进工程的自定义目录,比如App/Crypto。然后在魔术棒选项的C/C++选项卡里把这个目录加到Include Paths。这里有个细节:STM32的CMSIS头文件路径最好往前排,否则可能编译时先找错了头文件。

源文件加入工程后,编译若报错“未定义符号”,检查一下aes.c是不是真的加进了工程树,而不只是放在文件夹里。我见过太多次“文件在目录里但没被工程引用”的情况。还有一个容易被忽略的点:优化等级建议先设置到O0,等测试全部通过再改成O2验证性能,这个后面性能测试部分会展开说。

3. 加密库集成与三种模式的实战代码

3.1 AES算法原理速成

AES是个分组密码算法,明文和密文都是固定16字节一块。AES-128表示密钥长度16字节,整个加密过程执行10轮;AES-192是12轮,AES-256是14轮。每一轮会做四个操作:字节代换、行移位、列混淆、轮密钥加。

用大白话解释:字节代换就是把每个字节通过一张S盒表替换成另一个值;行移位是把状态矩阵的行错位挪动;列混淆是每列做一次数学变换,让单个字节的变化扩散到整列;轮密钥加则是把当前状态和本轮扩展出来的密钥做异或。10轮下来,明文的每个位都受到密钥和所有其他位的影响,这就是AES安全性高的重要原因。

3.2 ECB模式:先跑通最小闭环

ECB是最基础的AES模式,把16字节明文直接丢进去加密,得到16字节密文。为了验证函数调用没问题,我写了个最小闭环测试:定义一组密钥和明文,先调用加密函数,再调用解密函数,通过串口打印每一组数据。

实际使用的tiny-AES-c接口比较直观,流程是先用AES_init_ctx初始化上下文,然后对缓冲区原地加解密:

#include "aes.h" struct AES_ctx ctx; uint8_t key[16] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}; uint8_t buf[16] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; AES_init_ctx(&ctx, key); // 加密 AES_ECB_encrypt(&ctx, buf); printf("ECB encrypt: "); print_hex(buf, 16); // 解密 AES_ECB_decrypt(&ctx, buf); printf("ECB decrypt: "); print_hex(buf, 16);

打印结果那一步特别重要,如果密文和用在线工具算出来的一致,说明基本函数没问题。但我也要强调:ECB模式有个致命缺点,相同明文块永远得到相同密文块,真实数据里很容易泄露格式信息。这个模式在测试程序里只适合验证基础函数,不要直接用到产品里。

3.3 CBC模式:引入IV,消除明文模式残留

CBC是产品里最常见的模式之一,它的核心思想是:每个明文块先和上一个密文块做异或,再执行AES加密。第一个明文块没有“上一个密文”,所以引入一个16字节的初始化向量IV。这样即使两个明文块内容相同,加密后密文块也大概率不同,泄露信息的风险大大降低。

对应代码里,需要多准备一个IV。tiny-AES-c的初始化函数带IV版本是AES_init_ctx_iv,然后调用AES_CBC_encrypt_buffer处理一整段数据:

uint8_t iv[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; AES_init_ctx_iv(&ctx, key, iv); AES_CBC_encrypt_buffer(&ctx, buf, len); // 注意解密前需要重新设置相同的IV AES_init_ctx_iv(&ctx, key, iv); AES_CBC_decrypt_buffer(&ctx, buf, len);

这里有个特别容易踩的坑:解密时如果不重新初始化IV,而是沿用加密后已经被修改的IV值(因为库函数会更新IV),解密结果一定是错的。我一开始就是犯了这个错,折腾了半小时才反应过来。所以写代码时养成习惯,加解密前都重新执行一次AES_init_ctx_iv

3.4 CTR模式与盐的设计思考

CTR模式设计思路完全不同:它不是把明文直接加密,而是先对一个计数器值做AES加密,得到一段“密钥流”,再把密钥流和明文按位异或,从而得到密文。解密时用完全相同的计数器再一次生成密钥流,和密文异或就能还原明文。

CTR模式最大的优点是不需要补位,数据是几个字节就可以处理几个字节,非常适合长度不固定的通信包。它还有个特性是加密和解密用的是同一个函数(AES_CTR_xcrypt_buffer),不像CBC需要区分encrypt和decrypt。代码上自然就把IV替换成了“nonce + counter”的概念:

uint8_t nonce_counter[16] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; AES_init_ctx_iv(&ctx, key, nonce_counter); AES_CTR_xcrypt_buffer(&ctx, buf, len);

说到“盐”,很多人把盐和IV混为一谈,其实两者职责不同。盐通常用在密钥派生阶段,比如用户输入口令后,通过加盐、迭代哈希生成真正的AES密钥,用来对抗离线字典攻击;而IV是分组算法里用来让一组数据产生随机化效果的nonce。测试程序里我很自然地做了一个组合:让F4的TRNG外设生成一个随机数作为计数器初始值,再和固定测试密钥一起跑CTR模式,模拟真实协议里“随机数随密文一起传输”的场景。

至于“盐放后端”这个问题,我的理解是:盐不应该硬编码在设备固件里。设备端生成随机盐,要么把它附加在密文前一起发送,要么由服务端生成后下发给设备,总之盐要能动态变化。一旦盐被硬编码在固件里,逆向人员拿到固件就等于拿到了造盐规则,整个加密体系的安全度会大幅缩水。后面安全建议部分我还会再提一句。

4. 串口日志设计、测试用例与性能实测

4.1 让每个中间结果可观测

调试加密程序,最怕的就是黑盒。我专门写了几个打印函数,把密钥、明文、密文、解密结果全部以HEX格式输出到串口。每条日志带标记前缀,比如[AES-TEST] key:[AES-TEST] cipher:,这样用串口助手分析数据时一目了然。

日志设计有个原则:输出内容不要过多,每一条都有明确语义。我留了一个宏定义来控制日志开关,比如#define AES_DEBUG_ENABLE 1,正式工程里关掉即可,不用删代码。如果你用RTT代替串口,速度会更快,日志接入也非常方便,但裸机串口依然是调试最通用的路子。

4.2 测试用例设计与标准测试向量

测试用例怎么设计直接影响你能发现多少问题。我用了三组用例:

用例编号测试内容预期结果
TC1使用NIST SP 800-38A标准向量,验证AES-128 ECB加解密密文与标准值完全一致,解密后还原明文
TC2连续加密5个数据块,验证CBC模式数据块之间的关联性各块密文互不相同,解密后数据完全一致
TC3只修改明文的最后一个字节,对比加密结果密文变化远不止最后一个字节,体现雪崩效应

第一组用例是底线,算法正不正确就看它。第二组用例专门用来验证CBC模式下的块链接行为,如果你把某个密文块删掉,后续所有解密块都会变乱,这就是CBC的特性。第三组用例特别直观,你改一个字节明文,密文几乎全部变了,这在CTR模式下也能看到,因为CTR密钥流是固定的,但明文参与异或之后改变位置不同。

网上有很多开源测试工具可以帮你生成参考密文,核心是别自己拍脑袋发明向量,优先用标准向量。

4.3 性能实测:用DWT在MCU上计时

测AES性能最直接的方法是用Systick计时,但如果系统里其他模块占了Systick,就会冲突。我推荐用Cortex-M内核自带的DWT周期计数器,它不占用系统定时器,精度到CPU周期,特别适合这类短耗时测量。

初始化DWT的代码很简短:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

测耗时的时候,在加密函数前后分别读DWT->CYCCNT,相减后除以主频就得到秒数:

DWT->CYCCNT = 0; AES_CBC_encrypt_buffer(&ctx, buf, len); uint32_t cycles = DWT->CYCCNT; float ms = cycles / 168.0f / 1000.0f;

我实测的参考数据是:在STM32F407 @168MHz、Keil AC5编译器、优化等级O2的条件下,使用tiny-AES-c加密512字节数据,耗时大概在0.5ms量级,换算过来吞吐量接近1MB/s多。这个成绩对于传感器上报、指令加密场景完全够用;但如果一分钟要加密几兆字节的视频流或文件流,就得评估是否要用硬件AES。

测性能前务必确认优化等级,O0和O2的差距可能超过3倍。我要不是被O0下的慢速迷惑过,也不会特意强调这点。

5. 容易踩的坑与排查手册

5.1 Keil5编译配置类问题

测试程序写完后,最常报错的无非这几类:找不到头文件、未定义函数、变量被优化掉。找不到头文件就把Include路径重新检查一遍;未定义函数则是源文件没加进工程;而“变量被优化掉”多半是因为你把优化等级开到了O2,但调试时又想观察某个局部变量。处理办法是调试阶段统一O0,功能全部验证完再切O2。如果必须保持O2,可以给关键变量加volatile修饰,但我不建议为了调试去改产品代码结构。

还有一个隐性问题是芯片型号选错。你用的是F407,结果Device列表里不小心选了F103,Cortex-M内核虽然兼容编译,但启动文件和寄存器头文件不一致,跑起来必出问题。每次新建工程都先看下编译宏里有没有STM32F40XX字样,这一步能省很多后面的排查时间。

5.2 加解密结果校验失败的排查三板斧

加密结果不对,九成是下面三点:

第一,密钥和IV长度不对。AES-128要求16字节密钥,很多人直接把ASCII字符串当密钥用,比如"1234567890123456",这里每个字符是8位,数量也对,但取值是ASCII码,和你想表达的数字数组完全不是一回事。打印HEX出来一看就知道问题在哪。

第二,模式调用错误。CTR模式下加密和解密函数其实是同一个,但CBC和ECB必须严格区分encrypt和decrypt,调错了密文当然还原不回来。

第三,IV没有重置。CBC模式下库函数会更新内部IV状态,解密前必须重新调用初始化函数把IV设置回原值,用加密后的IV值去解密,结果必然乱七八糟。

排查时不要凭感觉猜,我把每一步数据都打印出来,然后和参考工具对比,很快就能定位是哪一环节出了问题。

5.3 安全建议与产品化注意点

测试程序跑通之后,要做成一个真正能上线的加密模块,还有几件事必须处理。第一,不要使用固定IV;第二,密钥最好由TRNG随机生成,或者按芯片唯一ID派生;第三,启用STM32的读保护,防止固件被直接抄读;第四,完整的加密协议远比单个AES算法复杂,AES只是原语,别试图自己发明加密协议。如果产品要走认证或合规路线,协议设计和安全评估该请专业的人来做就请专业的人来做。

代码层面还有两个优化方向:一是开启了编译器O2优化后性能会明显提升;二是如果对吞吐量不满意,可以把算法里的查表数据放到对齐的静态数组中,减少缓存加载次数。

最后分享一点个人体会:这个测试程序虽然小,但它把AES从“听过名字”变成了“手里有数据”的一步。测完三种模式之后,我把代码整理成了一个加密服务模块,后续给两个项目复用了,每次接入新主控只需要改底层打印接口。如果你也想在STM32上跑AES,我建议照这个思路先跑通再说。遇到数据对不上不要慌,八成是密钥或者IV的理解问题,把每个中间结果都打印出来,逐字节比对,问题很快就能定位。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:15:20

Axolot DOCXSuit:Delphi纯代码生成和读写DOCX的实战指南

简介:Axolot DOCXSuit 是一套面向 Delphi XE10.3 Rio 开发者的 DOCX 文档处理组件集,包含 AXWWriter、AXWReports 与 DOCXReadWrite 三部分,分别覆盖 Word 文档动态生成、可视化报表设计以及现有文档读写与批量修改等场景。借助这套工具&…

作者头像 李华
网站建设 2026/9/9 20:14:18

大漠插件Python注册助手:从COM组件原理到自动化测试环境搭建

简介:大漠插件7.2213的Python注册示例包,面向需要借助Python调用大漠插件实现自动化测试、网页元素定位与数据抓取的开发者,尤其适合已有一定Python基础、希望扩展桌面自动化能力的程序员。核心文件DmHelper.py演示了如何在Python环境中注册并…

作者头像 李华
网站建设 2026/9/9 20:12:08

WorkBuddy企业版落地指南:从个人工作台到团队协作自动化

1. 从“一个人一堆工具”到“一个团队一套系统” 先聊一个很现实的场景:作为一个开发者或者业务负责人,你本地可能装了十几个工具——待办清单、笔记软件、表格、IM、项目管理、文档库,再加上公司内部的各种后台系统。工具越多,信…

作者头像 李华
网站建设 2026/9/9 20:09:29

用COMSOL模拟锂枝晶生长:四种建模路径与工程实践指南

写锂枝晶仿真这几年,听到最多的需求就是:“能不能用COMSOL把枝晶长出来看看?”这个问题看着简单,真做起来却一点都不省心。锂枝晶涉及电化学沉积、离子传质、界面运动、力学损伤多个物理过程,不同阶段起主导作用的机制…

作者头像 李华