当前位置: 首页 > news >正文

嵌入式设备安全升级:从mbedtls库中移植AES-CMAC算法实战

1. 项目概述:为什么要在嵌入式里折腾CMAC?

最近在做一个物联网终端设备的安全升级项目,客户要求对固件升级包进行强完整性校验和来源认证。方案评审时,大家一致否决了简单的MD5或SHA-1,认为在当前的攻击环境下强度不够。最终,我们决定采用基于AES的CMAC算法。原因很简单:它算是消息认证码里的“实力派”,基于成熟的分组密码,安全性有保障,而且计算资源消耗相对可控,非常适合我们手头那些RAM和Flash都捉襟见肘的MCU。

但问题来了,我们主控芯片的SDK里并没有现成的CMAC实现。自己从头实现一个?密码学这东西,自己写容易出鬼,一个细微的时序或逻辑错误就可能导致严重的安全漏洞。于是,我们把目光投向了mbedtls这个在嵌入式领域声名显赫的密码学库。它模块化设计清晰,代码质量高,且拥有宽松的Apache 2.0许可证。我们的任务很明确:从mbedtls这个“大工具箱”里,把CMAC这个“精密工具”单独拆出来,移植到我们的裸机环境里,并且要确保它跑得稳、占得少。

这不仅仅是调用几个API那么简单。你需要理解CMAC在mbedtls里是怎么被“组装”起来的,它依赖哪些底层模块,如何裁剪掉我们用不上的部分,以及如何适配到没有操作系统、没有标准库的环境。这个过程,充满了对密码学原理、代码结构和嵌入式系统理解的考验。接下来,我就把这次移植mbedtls的CMAC算法的核心思路、实操步骤和踩过的坑,毫无保留地分享给你。

2. 核心思路与模块依赖分析

2.1 CMAC算法原理与mbedtls实现窥探

在动手移植前,必须搞清楚我们要搬的“家具”内部结构。CMAC,全称Cipher-based Message Authentication Code,其核心思想是利用分组密码(如AES)来生成一个固定长度的标签,用于验证消息的完整性和真实性。

mbedtls中的CMAC实现,位于library/cmac.c。阅读其源码,你会发现它并不是一个完全独立的黑盒。它的工作流程大致是:

  1. 子密钥生成:根据核心密钥K,通过加密一个全零数据块,并经过一系列移位和异或操作,生成两个子密钥K1和K2。这是CMAC算法的关键步骤。
  2. 消息分组处理:将输入消息按分组长度(AES是128位/16字节)分块。对前面的所有完整块,使用核心密钥进行加密。
  3. 最后块特殊处理:对最后一个块,根据其是否是完整块,选择与K1或K2进行异或后,再进行加密。
  4. 输出标签:取最后一次加密结果的左半部分(例如,对于128位AES-CMAC,取前16字节)作为最终的MAC值。

在mbedtls中,这个流程被封装成几个关键函数:mbedtls_cmac_setkey,mbedtls_cmac_update,mbedtls_cmac_finish。但更重要的是,这些函数高度依赖另一个模块:mbedtls_cipher。CMAC本身不直接操作AES的加密解密,它通过一个统一的cipher接口来调用具体的分组密码算法。这就意味着,要CMAC能跑,你必须至少准备好Cipher模块,以及一个具体的Cipher实现(比如AES)。

2.2 关键依赖模块拆解

因此,我们的移植清单变得清晰起来,这是一个自上而下的依赖链:

  1. 核心目标mbedtls/cmac.cinclude/mbedtls/cmac.h
  2. 直接依赖
    • mbedtls/cipher.c:提供统一的密码算法操作接口。
    • mbedtls/cipher_wrap.c:这个文件包含了具体算法(如AES)对cipher接口的“包装”实现,是连接抽象接口和具体实现的桥梁。
    • include/mbedtls/cipher.h
  3. 算法实现依赖
    • mbedtls/aes.c:我们需要的AES具体实现。这是计算的主力。
    • include/mbedtls/aes.h
  4. 平台适配依赖
    • mbedtls/platform.cmbedtls/platform_util.c:提供内存操作、随机数生成等平台相关函数的替代实现。在裸机环境,我们通常需要自己实现或裁剪它。
    • include/mbedtls/platform.hinclude/mbedtls/platform_util.h
  5. 基础工具依赖
    • mbedtls/bignum.c?等等,先别急!对于单纯的AES-CMAC,实际上并不依赖大数库。这是一个常见的误区。仔细查看源码,CMAC和底层AES的运算都在字节和字级别完成,不涉及大整数运算。这能为我们节省大量代码空间。

注意:这种模块化依赖分析是移植成功的第一步。盲目地把整个mbedtls库都搬过来,会让你的固件体积爆炸。一定要根据功能需求,按需抽取。

2.3 头文件与配置的迷宫

mbedtls通过一个名为mbedtls/config.h的配置文件来启用或禁用几乎所有功能。默认的config.h内容繁多。我们需要创建一个极简的版本。

你需要在这个配置文件中明确:

  • #define MBEDTLS_CMAC_C:启用CMAC模块。
  • #define MBEDTLS_CIPHER_C:启用Cipher抽象层。
  • #define MBEDTLS_AES_C:启用AES算法。
  • 很可能需要#define MBEDTLS_CIPHER_MODE_ECB:因为CMAC内部使用AES的ECB模式进行加密运算。
  • 关闭所有其他无关的宏,例如MBEDTLS_BIGNUM_C,MBEDTLS_SHA256_C,MBEDTLS_ENTROPY_C等。

头文件的包含路径也要处理好。你需要让编译器能找到mbedtls/目录下的头文件,并且你的config.h必须在包含路径中,或者直接放在mbedtls/目录下替换原文件。

3. 移植实操:从源码到可执行代码

3.1 源码文件抽取与工程集成

首先,在你的嵌入式项目目录下,创建一个子目录,比如third_party/mbedtls/。将分析好的必需源码文件复制进来:

third_party/mbedtls/ ├── include/ │ ├── mbedtls/ │ │ ├── aes.h │ │ ├── cmac.h │ │ ├── cipher.h │ │ ├── platform_util.h │ │ └── ... (其他必要的头文件) │ └── 你的 config.h 也可以放在这里,或单独管理 └── library/ ├── aes.c ├── cipher.c ├── cipher_wrap.c (尤其是 aes.c 对应的包装器,如 cipher_wrap.c 中与AES相关的部分) ├── cmac.c └── platform_util.c

这里有一个关键细节:cipher_wrap.c文件通常包含了所有算法的包装函数。你需要仔细查看,只提取与AES相关的静态函数(如static int aes_crypt_ecb_wrap)和相关的mbedtls_cipher_info_t结构体定义,而不是整个文件。更好的方法是,参考该文件,自己为AES实现一个精简的“包装器”,只暴露CMAC所需的接口。

然后,在你的IDE或Makefile中,将这些.c文件添加到编译源文件列表,并将include目录添加到头文件搜索路径。

3.2 平台适配层实现

这是移植中最容易出问题的一环。mbedtls库内部会调用一些标准库函数,如memset,memcpy,memcmp。在裸机环境下,你需要提供这些函数的实现。通常,编译器自带的运行时库(如newlib-nano)会提供它们,但有时为了更严格控制,我们会自己实现或使用芯片厂商提供的轻量级实现。

更重要的是mbedtls/platform_util.c中的函数。这个文件里有两个关键函数:

  • mbedtls_platform_zeroize():用于安全清零内存,防止敏感信息(如密钥)残留在内存中。在资源受限且没有MMU的MCU上,一个简单的memset实现可能因为编译器优化而被忽略。安全的实现可能需要使用volatile指针。这里给出一个参考实现:
    void mbedtls_platform_zeroize( void *buf, size_t len ) { volatile unsigned char *p = (volatile unsigned char *)buf; while( len-- ) { *p++ = 0; } }
  • mbedtls_calloc()mbedtls_free():CMAC模块内部默认使用动态内存分配来创建上下文结构体。在嵌入式系统中,动态内存分配是很多不稳定问题的根源,尤其是内存碎片。强烈建议修改配置或源码,改为静态分配。

修改方法:在config.h中定义#define MBEDTLS_CMAC_ALT,然后你自己实现一组mbedtls_cmac_xxx函数,在内部使用静态全局变量或由调用者传入的静态缓冲区。或者,更直接地,修改cmac.c,将mbedtls_cipher_context_t的分配改为使用静态数组。这需要你深入理解上下文结构,并小心处理多任务重入问题(如果存在RTOS)。

3.3 配置裁剪与编译优化

你的config.h文件是控制代码大小的总开关。除了开启必要的模块,还要关闭所有调试和冗余功能:

#define MBEDTLS_CMAC_C #define MBEDTLS_CIPHER_C #define MBEDTLS_AES_C #define MBEDTLS_CIPHER_MODE_ECB // 关闭大量不必要功能 #undef MBEDTLS_SELF_TEST // 非常重要!关闭自测代码,能省下大量空间 #undef MBEDTLS_ERROR_C // 关闭详细的错误字符串,只保留错误码 #undef MBEDTLS_VERSION_C #undef MBEDTLS_PLATFORM_TIME_ALT // ... 关闭其他所有你确认不需要的宏

编译时,开启最高级别的优化(如-Os优化尺寸,-O2/-O3优化速度),并确保消除未使用的函数和符号(GCC的-ffunction-sections -fdata-sections配合链接器的--gc-sections)。

3.4 编写测试向量验证

移植完成后,绝对不能假设它是正确的。必须使用标准测试向量进行验证。NIST官方提供了AES-CMAC的测试向量。你可以写一个简单的测试函数,放在主循环或通过调试接口调用。

#include “mbedtls/cmac.h” #include “mbedtls/aes.h” #include <stdio.h> // 或你的串口打印函数 int test_cmac(void) { mbedtls_cipher_context_t ctx; unsigned char key[16] = { ... }; // 测试密钥 unsigned char msg[] = { ... }; // 测试消息 unsigned char mac[16]; // 存放结果 const unsigned char expected_mac[16] = { ... }; // 预期结果 mbedtls_cipher_init(&ctx); // 选择AES-128-ECB算法(CMAC内部使用) mbedtls_cipher_setup(&ctx, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB)); mbedtls_cipher_cmac_starts(&ctx, key, 128); // 128位密钥 mbedtls_cipher_cmac_update(&ctx, msg, sizeof(msg)); mbedtls_cipher_cmac_finish(&ctx, mac); mbedtls_cipher_free(&ctx); if(memcmp(mac, expected_mac, 16) == 0) { my_printf(“CMAC test PASSED!\n”); return 0; } else { my_printf(“CMAC test FAILED!\n”); // 打印出计算得到的mac和预期的mac进行比对 return -1; } }

通过所有测试向量,才能证明你的移植在功能上是正确的。

4. 性能优化与资源管理实战

4.1 内存占用深度剖析

移植完成后,使用编译器的map文件分析内存占用是必修课。你会主要关注两部分:

  • Flash(代码)占用:由你编译进来的.c文件决定。通过裁剪config.h和移除无用函数,通常能将AES-CMAC相关的代码控制在10-20KB左右,这对于现代Cortex-M系列MCU来说是可以接受的。
  • RAM(数据)占用:这是更关键的资源。主要占用来自:
    1. 上下文结构体mbedtls_cipher_context_t和内部CMAC状态。查看结构体定义,估算其大小。AES-128的上下文大概在200字节左右。
    2. 栈空间:加解密运算过程中的局部变量和函数调用栈。确保你的任务栈或主栈有足够空间(通常预留1-2KB给密码运算比较安全)。
    3. 动态内存:如前所述,务必消除动态内存分配

一个实用的技巧是,将CMAC上下文定义为全局静态变量,或者作为模块内部静态变量。如果系统中有多个地方需要用到CMAC(但非并发),可以考虑设计一个带互斥锁的上下文复用机制,以节省RAM。

4.2 计算速度优化策略

在资源受限的MCU上,AES运算速度可能是瓶颈。优化可以从几个层面考虑:

  • 硬件加速:这是最有效的途径。许多现代MCU(如STM32的CRYP外设,NXP的CAU,ESP32的AES加速器)都内置了硬件AES引擎。mbedtls的AES模块通常通过MBEDTLS_AES_ALT宏来支持硬件加速实现。你需要查阅芯片手册,实现一组替代函数(如mbedtls_aes_encrypt_alt,mbedtls_aes_decrypt_alt),并在config.h中启用MBEDTLS_AES_ALT。这可以将AES运算速度提升数十甚至上百倍。
  • 查表法优化:如果无硬件加速,mbedtls的默认AES实现使用了查表法(T-table),这是一种以空间换时间的策略。它需要约4KB的查找表(只读,可放在Flash)。确保你的config.h中启用了MBEDTLS_AES_ROM_TABLES(如果表是常量)以获得最佳性能。禁用此选项会使用动态计算,速度慢但代码小。
  • 减少数据搬运:在mbedtls_cmac_update中,如果可能,尽量让输入数据直接来自设备接口(如SPI Flash),避免先读到一个大缓冲区再计算,可以边读边计算,节省RAM和时间。

4.3 安全注意事项

  1. 密钥安全:CMAC的强度完全依赖于密钥。绝对不要将硬编码的密钥明文存放在Flash中。应使用芯片的唯一ID(UID)结合安全启动流程派生密钥,或使用安全元件(SE)存储。
  2. 时序攻击:软件实现的密码算法可能受到时序攻击的威胁。虽然CMAC本身结构相对规整,但底层的AES实现和内存比较函数(如验证MAC时的memcmp)需要是常数时间的。mbedtls提供了一些常数时间函数(如mbedtls_ct_memcmp),在验证MAC值时应该使用它们。
  3. 清零上下文:运算结束后,务必调用mbedtls_cipher_free()或你自己的清理函数,它会调用platform_zeroize来清理上下文中的密钥和中间状态。

5. 常见问题排查与调试心得

5.1 链接错误与未定义符号

这是移植初期最常见的问题。通常是因为依赖关系没理清,或者配置文件config.h的宏定义不匹配。

  • 症状:链接器报错,提示undefined reference tombedtls_cipher_info_from_type'` 等。
  • 排查
    1. 检查对应的.c文件(这里是cipher.c)是否已加入编译。
    2. 检查config.h中是否定义了对应的宏(这里是MBEDTLS_CIPHER_C)。
    3. 使用编译命令gcc -E ...对出问题的源文件进行预处理,查看宏展开后,相关函数定义是否还存在。

5.2 计算结果不正确

  • 症状:测试向量通不过,计算出的MAC值与预期不符。
  • 排查步骤
    1. 确认密钥和消息输入:用十六进制打印函数,确保你传递给CMAC函数的密钥和消息字节序列完全正确,没有大小端问题。嵌入式系统中,从网络或存储设备读取的数据,字节序要特别注意。
    2. 单步调试AES基本运算:写一个最简单的AES-ECB加密测试,使用一个已知的密钥和明文,看加密结果是否正确。如果这里就错了,那么问题出在AES层。
    3. 跟踪CMAC子密钥生成:在cmac.ccmac_generate_subkeys函数内部设置断点或打印日志,查看生成的K1和K2是否正确。这是CMAC算法最容易出错的地方之一。
    4. 检查分组处理逻辑:特别是最后一块数据的处理,判断“是否是完整块”的逻辑是否正确。

5.3 内存越界与系统崩溃

  • 症状:程序运行CMAC计算后死机、重启或数据异常。
  • 排查
    1. 栈溢出:这是最大的嫌疑。增大当前任务的栈空间,或者将CMAC计算函数放到栈更充裕的任务中执行。
    2. 上下文结构体大小:确认你分配的mbedtls_cipher_context_t缓冲区大小足够。可以查看cipher.h中该结构体的定义,或者直接使用sizeof()获取。
    3. 动态内存分配:如果你没有成功禁用动态分配,检查你的堆(heap)大小是否足够。在启动文件或链接脚本中调整堆的大小。

5.4 性能不达标

  • 症状:计算一个MAC耗时过长,影响系统实时性。
  • 排查与优化
    1. 使用硬件加速:这是首要检查项。
    2. 编译器优化等级:确认编译时开启了-O2-Os
    3. 查找表位置:确认AES的T表被链接到了访问速度快的Flash区域(如ITCM或带缓存的区域),而不是慢速Flash。
    4. 剖析代码:使用MCU的周期计数器(如DWT->CYCCNT)对mbedtls_cipher_cmac_finish函数进行打点,确定耗时主要在哪一步。

移植mbedtls的CMAC,就像在嵌入式设备上搭建一个微型的安全堡垒。它不需要豪华的配置,但要求每一块砖都砌得扎实可靠。这个过程让我深刻体会到,在资源受限的环境下实现安全功能,平衡性能、体积和安全性是一门艺术。最终,当你的设备能够快速、稳定地输出正确的CMAC值时,那种成就感,是对所有调试和优化工作的最好回报。

http://www.jsqmd.com/news/1338822/

相关文章:

  • 如何免费实现PotPlayer字幕实时翻译:百度翻译插件终极指南
  • Linux系统Python环境管理:externally-managed-environment错误解析与虚拟环境实践
  • 深蓝词库转换:高效解决输入法词库迁移难题的完整指南
  • 榆林瓷砖空鼓松动不用全砸!全屋瓷砖翘边、起拱、渗水完整维修科普 - 宅安选房屋修缮
  • NS-USBloader:一站式解决Switch文件传输的终极工具
  • 厦门简会荣获“数据要素X”大赛三等奖
  • 济南改灯靠谱店在哪儿?实测对比后,后浪改灯首当其冲 - Ayu8888
  • 智微工业工控机在激光行业的技术架构与性能特点分析
  • AI 自动化办公实践:OpenClaw 双操作系统完整落地教程(含安装包)
  • 果蔬采摘机器人末端执行器设计:从夹剪一体到多臂协同的工程实践
  • 三月七小助手:星穹铁道自动化助手完整配置指南
  • 揭秘正规网站建设报价背后的真相:从隐形消费到价值重塑的真诚对话
  • 新能源行业高盐废水处理厂家怎么选?这家处理10t/h混盐废水,年创收1.2亿元 - 资讯在线
  • 深蓝词库转换:30+输入法格式互转终极解决方案
  • 网盘下载速度太慢?3分钟实现免费提速方案
  • 如何高效配置Windows多媒体解码器:LAV Filters终极实战指南
  • 终极指南:如何使用NS-USBLoader一站式管理你的Switch游戏库
  • LLM 核心原理:模型如何生成答案
  • UE5数字人表情驱动实战:LiveLinkFace与ARKit BlendShape映射指南
  • 2026年评价高的工正UV平板机厂家,大幅面设备客户口碑力荐 - 工业设备
  • 【具身智能】人形机器人量产前,如何验证其在不同场景下的可靠性和安全性?
  • 论文英文摘要别瞎翻[特殊字符]‍♀️这款AI论文软件,才是学术翻译的正确打开方式
  • OpenClaw本地AI助手配置指南:从模型接入到技能开发
  • Cesium PolygonGeometry 添加面完整知识点 TS 代码
  • Data Struct
  • 废水零排放项目蒸发器厂家怎么选?四个维度锁定靠谱供应商 - 资讯在线
  • 如何在3分钟内免费实现Windows虚拟手柄完美仿真
  • GB/T 2423环境试验标准深度解析:从原理到实践的应用指南
  • 游戏与工业可视化模型优化:从UE5 Nanite到LOD的技术哲学差异
  • 夏季切削液发臭不用瞎换液