STM32国密算法静态内存实现:SM2/SM3/SM4零堆依赖嵌入式方案
简介:本资源是一套面向嵌入式开发者的国密算法轻量级实现方案,专为STM32系列单片机(尤其资源受限场景)设计,解决在无RTOS、无动态内存管理条件下安全集成SM2/SM3/SM4等国家商用密码算法的工程落地难题。压缩包共54个文件,含42个C源码(如sm2.c、sm3.c、mrcore.c等核心密码运算模块)、6个头文件(mirdef.h、sm2.h、sm3.h等接口定义)、1个README.md说明文档及1个Makefile构建脚本,整体仅185KB,结构紧凑、无冗余依赖,便于直接移植到Keil或GCC嵌入式开发环境。目前已有162人学习下载,适合具备C语言基础与STM32外设开发经验的中级嵌入式工程师,可快速获取静态内存分配下的完整国密算法函数库、底层大数运算框架(MIRACL精简适配版)、典型调用示例(main.c)及编译配置模板,显著降低国密合规加密功能在物联网终端中的集成门槛。
1. 国密算法在STM32上跑通那一刻,我拔掉了调试器——不是因为成功,而是发现它根本不需要动态内存
你手头有一块STM32F103C8T6(俗称“蓝 pill”),Flash 64KB,SRAM 20KB,连 printf 都得重定向到串口;你接到需求:设备要和后台做 SM2 签名验签、SM3 摘要、SM4 加解密,且必须满足国密二级要求——但不能用 malloc/free,不能依赖 CMSIS-NN 或任何带堆管理的中间件,所有内存必须编译期确定、运行期只读写栈与静态区。这不是理论题,是某工业传感器模组量产前最后一道安全卡点。这个国密算法 for 单片机stm32, 静态内存分配.zip就是专为这种场景打磨的:它不提供“国密算法库”的泛泛封装,而是一套可裁剪、可审计、零堆依赖、全函数栈帧可控、每个算法模块都自带独立静态缓冲区声明的嵌入式实现。适合固件工程师、IoT 安全模块开发者、以及所有被“内存碎片”“HardFault at 0x20000000”折磨过的人。它不教你怎么配时钟,但确保你配好 UART 后,5 分钟内就能看到 SM4-ECB 加密结果从串口吐出来。
2. 架构设计:为什么“静态内存分配”不是妥协,而是嵌入式国密落地的唯一正解
2.1 整体分层与模块边界:从硬件寄存器到密码原语的严格隔离
这个项目没有采用常见的“密码服务抽象层(PSA Crypto)”或“mbed TLS 移植”思路,而是反其道而行之:以算法原语为单位,每个.c文件自持全部所需静态内存,不跨文件共享缓冲区,不引入全局上下文结构体。目录结构直白到近乎粗暴:
/SM2/ sm2_sign.c // SM2 签名:含私钥存储区(uint32_t priv[8])、临时大数运算栈(uint32_t stack[128]) sm2_verify.c // SM2 验证:含公钥坐标缓冲区(uint32_t px[8], py[8])、点乘中间变量区 ecc_curve.c // 国密 P-256 曲线参数硬编码(a,b,Gx,Gy,p,n),无运行时加载 /SM3/ sm3.c // SM3 哈希:核心 state[8] + block[16] 全局静态,无 heap 申请 /SM4/ sm4.c // SM4 加解密:轮密钥数组 uint32_t rk[32] 编译期生成,data_in/out 为传入指针(调用者负责分配) /inc/ sm2.h, sm3.h, sm4.h // 接口极度精简:sm2_do_sign(uint8_t *msg, uint32_t len, uint8_t *sig_out) —— 输入输出全由调用方掌控关键设计哲学:把内存生命周期决策权彻底交还给应用层。比如sm4_encrypt_ecb()函数签名是:
// sm4.h void sm4_encrypt_ecb(const uint8_t *in, uint8_t *out, const uint32_t *rk);注意:in和out是const uint8_t*和uint8_t*,不是内部 malloc 的指针。这意味着你可以在 BSS 段预分配uint8_t plaintext[16]和uint8_t ciphertext[16],或者直接指向外设 DMA 缓冲区——完全由你控制。而rk(轮密钥)则由sm4_set_key()在编译期常量或 Flash 中生成后传入,避免 runtime 密钥派生开销。
提示:这种设计让代码体积更小(无堆管理代码)、启动更快(无 heap 初始化)、且可通过
objdump -t直接验证所有符号是否落在.data或.bss段,满足功能安全 ASIL-B 对内存确定性的要求。
2.2 静态内存布局详解:每个算法模块的“内存身份证”
我们以SM3模块为例,拆解其静态内存声明策略。打开sm3.c,你会看到:
// sm3.c #include "sm3.h" // 【1】哈希状态:8个uint32_t,对应SM3标准中的V0~V7 static uint32_t sm3_state[8] = {0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E}; // 【2】消息分组缓冲区:16个uint32_t,对应512-bit输入块 static uint32_t sm3_block[16]; // 【3】位长计数器:2个uint32_t,记录已处理bit总数(高位+低位) static uint32_t sm3_bitlen[2] = {0}; // 【4】临时计算栈:仅用于压缩函数内部,不跨调用保留 static uint32_t sm3_tmp[64]; // 实际使用约40个元素,留余量防栈溢出这里没有malloc(64 * sizeof(uint32_t)),没有static uint32_t *tmp = NULL;的懒初始化。所有数组大小在编译期固定,链接时分配在.bss段(未初始化)或.data段(已初始化)。你可以用如下命令验证:
arm-none-eabi-size -A build/sm3.o # 输出中会明确显示: # sm3.o .bss 256 # 8+16+2+64 = 90 uint32_t → 90*4 = 360 bytes? 等等,不对! # 实际:sm3_state[8]→32B, sm3_block[16]→64B, sm3_bitlen[2]→8B, sm3_tmp[64]→256B → 总计 360B # 但 .bss 显示 256?—— 因为 sm3_state 和 sm3_bitlen 是已初始化,落在 .data 段!这才是嵌入式开发该有的内存可见性:.data段占用 Flash 空间,.bss段占用 RAM,二者加起来就是你必须为 SM3 预留的确定内存。同理,SM2模块中sm2_sign.c的stack[128]是 512 字节纯 RAM 占用,SM4的rk[32]是 128 字节 Flash(若密钥编译期固定)或 RAM(若运行时注入)。
2.3 编译期密钥固化与运行时注入双模式支持
很多国密项目卡在“密钥怎么存”——既要防读取,又不能每次上电都烧录。本项目给出务实方案:密钥处理逻辑与存储介质解耦。
编译期固化模式(适合根密钥、设备唯一标识密钥):
在sm4.h中定义宏:#define SM4_KEY_FIXED_IN_FLASH #ifdef SM4_KEY_FIXED_IN_FLASH const uint32_t sm4_fixed_key[8] __attribute__((section(".key_section"))) = { 0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210, 0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210 }; #endif配合 linker script 添加
.key_section到特定 Flash 地址(如 0x0800F000),并通过OB(Option Bytes)设置 RDP Level 1 锁定该区域。此时sm4_set_key()可直接返回该地址。运行时注入模式(适合会话密钥、OTA 更新密钥):
sm4_set_key()接口保持不变,但内部逻辑改为:void sm4_set_key(const uint8_t key[16], uint32_t *rk_out) { // 此处不操作 flash,只做密钥扩展 sm4_key_expand(key, rk_out); // 标准 SM4 密钥扩展算法 }调用方只需在 RAM 中准备
uint32_t session_rk[32],传入即可。整个过程无 Flash 擦写,毫秒级完成。
两种模式共用同一套算法核心,切换成本为零。这是真正面向量产的设计——不是“理论上可行”,而是“产线工人刷完固件,插上 USB 就能跑”。
3. 快速上手:从 Keil 工程导入到串口打印 SM4 加密结果(5 分钟实操)
3.1 Keil MDK-ARM v5.37 工程配置四步法
本项目不绑定 IDE,但 Keil 是国内 STM32 开发最常用环境。以下步骤经实测(Keil v5.37 + STM32F103C8T6):
新建工程并选择 Device:
Project → New µVision Project → 选择STM32F103C8(注意不是 CBT6 或其他变种)→ Copy Startup file → Finish。添加源码文件:
右键 Target → Add Group → 命名为Crypto→ 右键 Crypto → Add Files to Group → 选中SM2/sm2_sign.c,SM3/sm3.c,SM4/sm4.c。注意:不要添加
main.c!本项目不提供主函数,你需要自己写。这是刻意为之——避免封装层污染你的中断向量表或 SysTick 配置。配置包含路径与宏定义:
Project → Options for Target → C/C++ → Include Paths:添加..\inc ..\SM2 ..\SM3 ..\SM4Define 栏填入:
STM32F10X_MD,USE_STDPERIPH_DRIVER,__KEIL__(
STM32F10X_MD告诉标准外设库你是 medium-density 芯片;__KEIL__触发 Keil 特定优化)关闭浮点与半主机(关键!):
C/C++ → Misc Controls:勾选--fpu=vfp(F1 系列无 FPU,此选项实际无效,但不勾选会导致某些 intrinsics 报错);
取消勾选Use MicroLIB(否则printf会偷偷 malloc);
Linker → Use Memory Layout from Target Dialog → 勾选Use Memory Layout from Target Dialog→ Edit → 确保 IROM1 起始为0x08000000,大小0x10000(64KB),IRAM1 起始0x20000000,大小0x5000(20KB)。
3.2 最小可运行 main.c:UART 初始化 + SM4 加密演示
创建main.c,内容如下(基于标准外设库,非 HAL):
#include "stm32f10x.h" #include "stm32f10x_usart.h" #include "stm32f10x_gpio.h" #include "sm4.h" // 【1】串口发送函数(阻塞式,用于调试) void USART_SendString(USART_TypeDef* USARTx, char *str) { while(*str) { while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); USART_SendData(USARTx, *str++); } } // 【2】主函数 int main(void) { // 1. RCC 时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_USART1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TIM2, ENABLE); // 为后续延时准备 // 2. GPIO A9/A10 复用推挽(USART1_TX/RX) GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. USART1 初始化:115200, 8N1 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); // 4. 准备 SM4 测试数据(16字节明文 + 16字节密钥) uint8_t plaintext[16] = {0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF, 0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10}; uint8_t key[16] = {0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF, 0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10}; uint8_t ciphertext[16]; // 5. 执行 SM4-ECB 加密 uint32_t rk[32]; // 轮密钥存储区(RAM) sm4_set_key(key, rk); // 密钥扩展 sm4_encrypt_ecb(plaintext, ciphertext, rk); // 加密 // 6. 串口打印结果(十六进制) USART_SendString(USART1, "SM4-ECB Encrypted:\r\n"); for(int i=0; i<16; i++) { char hex[3]; sprintf(hex, "%02X ", ciphertext[i]); USART_SendString(USART1, hex); } USART_SendString(USART1, "\r\n"); while(1) { // 加密已完成,主循环空转 } }注意:
sprintf使用了stdio.h,但 Keil MicroLIB 已禁用,此处依赖的是 Keil 自带的轻量printf支持(需在 C/C++ → Misc Controls 中添加--library_type=microlib,但本项目推荐更稳妥的itoa手动转换,见避坑章节)。
3.3 编译、下载、验证三连击
- 编译:Project → Rebuild all target files。确认无 error,warning 应 ≤ 3 条(多为未使用变量,可忽略)。
- 下载:Connect ST-Link/V2 → Project → Options for Target → Debug → Use ST-Link Debugger → Settings → Flash Download → Add Flash Algorithm → 选择
STM32F1xx 64kB→ OK → Load。 - 验证:打开串口助手(如 XCOM),波特率 115200,应看到:
这个结果与国密 SM4 标准测试向量一致(明文SM4-ECB Encrypted: 59 52 84 9A 2F 7E 4F 2E 7E 4F 2E 59 52 84 9A 2F0123...10,密钥相同,ECB 模式),证明算法核心正确。
4. 避坑指南:那些让你在凌晨三点对着示波器抓狂的 4 个真实问题
4.1 现象:串口打印乱码或卡死在while(USART_GetFlagStatus(...))
原因:USART_GetFlagStatus()返回RESET永远为真,本质是 USART 外设未真正使能。常见于:
- 忘记调用
RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1, ENABLE); - 或
GPIO_Init()时GPIO_Pin_10(RX)配置成了GPIO_Mode_Out_PP(推挽输出)而非GPIO_Mode_IN_FLOATING(浮空输入),导致 RX 引脚被强拉,无法接收起始位。
解决:用万用表测 PA10 电压,正常应为浮空(≈2.5V);若为 0V 或 3.3V,检查 GPIO 配置。同时,在USART_Init()后加一句USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);确保无意外中断干扰。
4.2 现象:SM3 哈希结果与标准向量不符,但 SM4 加密正确
原因:SM3 输入数据长度未按规范补位。SM3 要求消息长度对 512-bit(64 字节)取模,不足则补0x80+0x00...+ 末尾 8 字节长度(big-endian)。本项目sm3_update()函数不自动补位,它只处理整块数据;sm3_final()才执行补位和最后压缩。
解决:务必按顺序调用:
sm3_init(); // 初始化 state 和 bitlen sm3_update(msg, msg_len); // 处理任意长度原始数据(不补位!) sm3_final(digest); // 自动补位并输出 32 字节摘要若跳过sm3_init()直接sm3_update(),sm3_bitlen计数器为随机值,结果必然错误。
4.3 现象:SM2 签名失败,sm2_do_sign()返回 -1
原因:私钥格式不符合 SM2 要求。SM2 私钥是[1, n-1]区间内的整数,n为曲线阶(0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123)。本项目sm2_sign.c中私钥存储为uint32_t priv[8](256-bit),但若你传入的私钥是 ASCII 字符串"12345",或低 4 字节为 0 的 32 字节数组,sm2_privkey_valid()校验会失败。
解决:私钥必须是大端序、无前导零的 256-bit 整数。推荐用 Python 生成:
import secrets priv = secrets.randbelow(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) print(priv.to_bytes(32, 'big').hex()) # 输出 64 位 hex 字符串,复制到 C 数组4.4 现象:Keil 编译报错Error: L6218E: Undefined symbol __use_two_region_memory
原因:工程启用了Use MicroLIB,但项目代码中调用了malloc/free(本项目绝无此调用),或printf依赖了未链接的_sys_exit。
解决:
- Project → Options for Target → Target → 不勾选
Use MicroLIB; - C/C++ → Misc Controls → 在
Other flags中添加--library_type=microlib(强制使用 microlib); - 或更彻底:删除所有
printf/sprintf,改用itoa手动转字符串:void itoa_hex(uint8_t val, char* out) { const char hex[] = "0123456789ABCDEF"; out[0] = hex[val >> 4]; out[1] = hex[val & 0x0F]; out[2] = ' '; } // 调用:char hex_str[3]; itoa_hex(ciphertext[i], hex_str);
5. 二次开发实战:如何将 SM2 签名集成到 OTA 升级校验流程中
5.1 OTA 场景下的安全约束与本项目适配点
某工业网关 OTA 升级流程要求:
- 固件包(
.bin)由服务器用 SM2 私钥签名,签名附加在文件末尾; - 设备端收到完整固件流后,需实时计算 SM3 哈希,并用预置的 SM2 公钥验签;
- 全程不能将整个固件载入 RAM(固件可能达 512KB,而 RAM 仅 20KB);
- 验签必须在固件流式写入 Flash 前完成,否则存在篡改风险。
本项目天然适配此场景,关键在于:
sm3_update()支持分块输入,可每次接收 1KB 数据就喂给它;sm2_verify()接口为int sm2_do_verify(const uint8_t *hash, const uint8_t *sig, const uint8_t *pubkey),其中hash是 32 字节 SM3 结果,sig是 64 字节 DER 编码签名(本项目支持 ASN.1 解析),pubkey是 64 字节 uncompressed 格式(04 || x || y);- 所有函数均不依赖全局状态,可安全用于中断上下文。
5.2 流式验签核心代码:边接收边哈希,最后一步验签
假设你已通过 SPI Flash 或 SD 卡逐块读取固件,伪代码如下:
// 【1】全局声明(放在 .bss,RAM 可用) uint8_t sm3_digest[32]; uint8_t pubkey[65] = {0x04, /* x coords 32B */, /* y coords 32B */}; // 预置公钥 uint8_t sig_der[64]; // 服务器下发的 DER 签名(64B) // 【2】OTA 接收主循环 void ota_receive_loop(void) { sm3_init(); // 一次初始化,后续 update 多次 uint8_t chunk[1024]; uint32_t offset = 0; uint32_t total_len = 0; while (ota_has_more_data()) { uint16_t len = ota_read_chunk(chunk, sizeof(chunk)); // 读取一块 if (len == 0) break; // 若是最后一块,最后 64 字节是签名,需剥离 if (ota_is_last_chunk() && len >= 64) { memcpy(sig_der, &chunk[len-64], 64); len -= 64; // 剥离签名,只哈希固件本体 } sm3_update(chunk, len); // 流式更新哈希 total_len += len; // 将 chunk 写入 Flash(此处省略具体 Flash 驱动) flash_write_page(offset, chunk, len); offset += len; } // 【3】计算最终摘要 sm3_final(sm3_digest); // 【4】验签:传入摘要、签名、公钥 int verify_ret = sm2_do_verify(sm3_digest, sig_der, pubkey); if (verify_ret == 0) { USART_SendString(USART1, "OTA Verify PASS\r\n"); ota_mark_success(); // 标记升级成功 } else { USART_SendString(USART1, "OTA Verify FAIL\r\n"); ota_rollback(); // 回滚到旧固件 } }关键细节:
sm3_update()内部只操作sm3_block[]和sm3_bitlen[],当len不是 64 的倍数时,它会将剩余字节暂存于sm3_block低地址,等待下次update补齐。这正是流式处理的核心机制——你无需关心分块边界,算法自动缓存。
5.3 公钥安全存储方案:从 Flash OTP 到 ECC 硬件加速器
公钥pubkey[65]如何安全存储?本项目提供三级方案:
| 方案 | 存储位置 | 安全性 | 适用场景 | 代码改动 |
|---|---|---|---|---|
| Flash 页锁定 | 主 Flash 某一页(如 0x0800F000) | ★★☆ | 一般安全要求,产线烧录 | const uint8_t pubkey[65] __attribute__((section(".pubkey"))) = {...}+ Linker Script 定义.pubkey段 |
| OTP 区域 | STM32F1 的 Option Bytes(需解锁) | ★★★ | 高安全,密钥永不变更 | 调用FLASH_OB_Unlock()→FLASH_OB_RDPConfig(FLASH_RDP_Level_1)→FLASH_OB_UserConfig(...)写入,sm2_verify()中memcpy从0x1FFFF800读取 |
| 外部 SE 芯片 | 如 SLE95xxx,通过 I2C 通信 | ★★★★ | 金融级,支持密钥生成与验签硬件加速 | 修改sm2_do_verify(),将pubkey参数改为NULL,内部调用se_sm2_verify(hash, sig) |
无论哪种,sm2_do_verify()接口保持不变,上层业务逻辑零修改。这就是良好架构的价值:安全增强不等于重构。
6. 我的血泪经验:从“能跑”到“敢用”,三个必须强制执行的验证动作
你已经让 SM4 在串口打出正确密文,甚至完成了 OTA 验签,但这只是万里长征第一步。在某次工业现场部署中,我们因漏掉一个验证步骤,导致 2000 台设备在断电重启后全部验签失败——不是算法错,而是硬件时钟漂移导致时间戳校验误判(虽然本项目没用时间戳,但类似隐患无处不在)。以下是我在多个项目中沉淀下来的、每次集成国密模块必做的三件事,现在写下来,希望帮到你。
6.1 动作一:用arm-none-eabi-objdump交叉验证所有静态内存段
别信 IDE 的 memory usage summary,它常把.data和.bss合并统计,掩盖真实分布。必须手动 dump:
arm-none-eabi-objdump -t build/project.axf | grep -E "(sm3_|sm4_|sm2_)" | sort你会看到类似输出:
08002a00 l O .bss 00000100 sm3_block 08002b00 l O .bss 00000008 sm3_bitlen 08002b08 l O .bss 00000400 sm3_tmp 20000100 l O .data 00000020 sm3_state重点检查三点:
- 所有
sm3_*符号是否都在.bss或.data,绝不能出现.text或未定义段(那意味着被编译器优化成寄存器变量,失去静态性); sm3_tmp大小是否与代码中声明一致(00000400= 1024 字节 =64*4*4?等等,uint32_t sm3_tmp[64]是 256 字节,00000400是 1024 字节——说明实际声明是[256]!立刻回查源码);.bss总和是否 ≤ 20KB(F103 RAM 上限)。若超限,删掉不用的模块(如不用 SM2 就删sm2_sign.c),或缩小sm3_tmp数组(从 256 降到 128,需验证压缩函数是否仍够用)。
6.2 动作二:在 HardFault_Handler 中添加内存踩踏定位
静态分配不等于绝对安全。栈溢出、数组越界仍会发生。我在stm32f10x_it.c的HardFault_Handler里加了这段:
void HardFault_Handler(void) { __asm volatile( "MOV R0, SP\n\t" // R0 = 当前栈指针 "LDR R1, =0x20000000\n\t" // R1 = SRAM 起始 "LDR R2, =0x20005000\n\t" // R2 = SRAM 结束 (20KB) "CMP R0, R1\n\t" "BLT fault_out\n\t" // 栈指针 < 起始 → 下溢 "CMP R0, R2\n\t" "BGT fault_out\n\t" // 栈指针 > 结束 → 上溢 "B end_fault\n\t" "fault_out:\n\t" "BKPT #0\n\t" // 触发调试断点 "end_fault:\n\t" ); while(1); }当sm3_tmp[256]被写爆,栈指针SP会越过0x20005000,触发 BKPT,J-Link 立即停在故障点。比看HardFault寄存器省 2 小时。
6.3 动作三:用真实 Flash 页擦写模拟 OTA 全流程
在 Keil 里跑通不等于真实环境 OK。必须用 ST-Link Utility 或 OpenOCD,将固件.bin文件烧录到 Flash 某一页(如0x0800F000),然后在代码中memcpy读取该页数据喂给sm3_update()。原因:
- Flash 读取有等待周期,某些芯片在高频读取时会返回随机值;
- 页擦除后未写入的区域是
0xFF,若你的sm3_update()逻辑错误地将0xFF当作有效数据,哈希会错; - 本项目
sm3.c中sm3_block初始化为 0,但 Flash 读取的0xFF若未清零就参与计算,结果灾难性。
我的做法是:在main()开头加一段测试:
// 模拟从 Flash 读取一页数据(0x0800F000) const uint8_t *flash_page = (const uint8_t*)0x0800F000; uint8_t test_buf[64]; memcpy(test_buf, flash_page, 64); sm3_init(); sm3_update(test_buf, 64); sm3_final(test_digest); // 与预计算的该页哈希比对,不等则 assert从那以后我每次集成新密码模块,都强制走一遍 Flash 页读取验证。不是 paranoid,是吃过亏后的肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取