资讯详情

嵌入式SM2国密算法实现:基于Miracl的轻量级密码学实践

📅 2026/9/12 18:24:16 | 华诺云谱 👁 阅读
嵌入式SM2国密算法实现:基于Miracl的轻量级密码学实践
简介本资源是一套基于Miracl大数运算库实现国密SM2椭圆曲线密码算法的纯C语言工程面向密码学初学者、嵌入式/系统级开发者及国密算法落地实践者解决SM2加密与数字签名在Linux/Windows双平台可编译、可调试、可集成的核心需求。压缩包共51个文件含41个C源码涵盖SM2核心逻辑、Miracl底层运算模块如mrpower.c/mrcurve.c等、5个头文件sm2.h/mirdef.h等、4份权威PDF文档SM2/SM3算法标准及推荐参数和1个Linux Makefile总大小1.55MB结构清晰便于按模块理解ECC数学基础与工程实现。已有765人学习下载读者可直接获取完整可运行的SM2加解密与签名验签示例main.c驱动、跨平台构建指引、Miracl库内联集成方案以及配套国密标准文档显著降低从理论到代码落地的学习门槛。1. 为什么在嵌入式或资源受限场景下仍要手写 SM2 加密与签名——而不是直接调用 OpenSSLSM2 国密算法不是“加个库就能跑”的黑盒。当你面对 STM32F103C8T6 这类仅有 20KB RAM、无浮点协处理器的 MCU或需在 Linux 内核模块中做透明加密、又或要通过静态链接规避动态依赖时OpenSSL 的 3MB 二进制体积、POSIX 线程依赖、堆内存频繁分配等特性反而成了系统瓶颈。此时MiraclMultiprecision Integer and Rational Arithmetic C Library的价值就凸显出来它是一套纯 C 实现、可裁剪、无外部依赖的大数运算内核专为密码学底层设计——SM2 所需的椭圆曲线点乘、模幂、模逆、随机数生成等全部构建在其big,ecp,zzn等模块之上。本实现不封装成 API 接口层而是直击 SM2 标准GM/T 0003.2—2012的数学流程从密钥对生成、公钥压缩格式解析、Z 值计算、ECIES 加密流程到 ECDSA 风格的签名生成与验证每一步都用 Miracl 原语逐行展开。适合需要可控内存占用、可审计算法路径、或需移植到裸机/RTOS 的开发者。2. 搭建 Miracl 环境并裁剪出 SM2 最小依赖集Miracl 库本身是高度可配置的其miracl.h中的宏开关决定了编译后体积与功能边界。盲目全量编译会导致生成 500 函数的.a文件而 SM2 仅需有限子集大整数运算big、素域椭圆曲线ecp、有限域算术zzn、伪随机数生成rand。关键不是“装上 Miracl”而是“只留下 SM2 要的那几块”。2.1 下载与目录结构准备Miracl 官方源码v6.2 或 v7.0解压后目录如下miracl/ ├── c/ # C 封装层SM2 纯 C 实现中禁用 ├── c/ # 核心 C 源码必须 │ ├── big.c # 大整数核心必选 │ ├── ecp.c # 椭圆曲线点运算SM2 必选 │ ├── zzn.c # 有限域 Z_p 算术SM2 必选 │ ├── rand.c # PRNGSM2 签名需随机 k必选 │ └── ... # 其余如 fp.c、ecn.c 等非必需 ├── include/ │ └── miracl.h # 主头文件需按需修改宏 └── config/ # 平台配置模板选 arm-none-eabi 或 linux-x86_64提示不要使用make全量编译。SM2 不需要ecn超椭圆曲线、fp浮点模拟、ff特征 2 域等模块强行包含会引入未定义符号和冗余代码。2.2 配置miracl.h关闭无关功能启用 SM2 所需能力打开include/miracl.h定位到平台相关宏段通常在#ifdef MR_GENERIC_MT附近按以下方式修改/* 关闭所有非必需模块 */ #define MR_NO_FILE_IO /* 不用文件读写SM2 密钥走内存传递 */ #define MR_NO_STDIO /* 避免 printf 依赖 */ #define MR_NO_RANDSEED /* 种子由调用者提供不依赖 time() */ #define MR_NO_VERSION /* 去掉版本字符串节省 ROM */ #define MR_NO_DIV_RANDOM /* SM2 不用随机除法 */ #define MR_NO_CRYPTO /* 此宏会禁用 crypto 相关函数 —— 注意必须注释掉否则 ecp_* 不可见 */ /* 启用 SM2 必需能力 */ #define MR_FLASH /* 启用 flash 存储支持可选用于密钥固化 */ #define MR_STATIC /* 强制静态分配避免 malloc */ #define MR_ALWAYS_BINARY /* 统一用二进制 I/O适配 DER 编码 */ #define MR_SEED_BYTES 32 /* SM2 签名要求 256 位随机 k设为 32 字节 */ /* SM2 曲线参数y² x³ ax b mod p其中 p 为 NIST P-256 同级素数 */ #define MR_BITS 256 /* 必须设为 256SM2 基于 256 位素域 */ #define MR_TOOLS /* 启用工具函数如 mr_shiftbits */ #define MR_OS_THREADS /* 若在 RTOS 下多任务运行保留裸机则注释 */2.3 编译最小静态库只含 big/ecp/zzn/rand在c/目录下执行以 Linux x86_64 为例交叉编译同理gcc -c -O2 -I../include -DMR_NO_FILE_IO -DMR_NO_STDIO \ -DMR_NO_RANDSEED -DMR_STATIC -DMR_BITS256 \ big.c ecp.c zzn.c rand.c -o miracl_sm2.o ar rcs libmiracl_sm2.a miracl_sm2.o验证导出符号是否精简nm libmiracl_sm2.a | grep -E (big_|ecp_|zzn_|irand) | wc -l # 正常应输出 ≈ 120–150 个符号全量版超 800注意ecp.c依赖big.c和zzn.c但zzn.c又依赖big.crand.c依赖big.c。四者顺序不可颠倒。若链接时报undefined reference to mr_burn说明漏了big.c中的内存擦除函数需确认-DMR_STATIC是否生效。3. 用 Miracl 原语实现 SM2 密钥生成、加密与签名全流程SM2 标准要求严格遵循国密规范中的字节序、编码格式与中间值计算顺序。本节不封装成sm2_encrypt()函数而是展示如何用 Miracl 原始类型big,ecp,zzn一步步构造 SM2 所需对象并完成核心运算。3.1 初始化上下文与加载 SM2 曲线参数SM2 使用素域曲线y² x³ ax b mod p其中p,a,b,Gx,Gy,n均为标准固定值见 GM/T 0003.2—2012 表 1。Miracl 中需显式加载#include miracl.h // 全局 curve context单例避免重复初始化 static ecp *sm2_curve NULL; static big p, a, b, Gx, Gy, n; void sm2_init_curve() { if (sm2_curve) return; // 分配大数空间MR_BITS256 → 32 字节 p mirvar(0); a mirvar(0); b mirvar(0); Gx mirvar(0); Gy mirvar(0); n mirvar(0); // 加载 SM2 素数 p十六进制字符串共 64 字符 cinnum(p, FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF); cinnum(a, FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC); cinnum(b, 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93); cinnum(Gx, 32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7); cinnum(Gy, BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0); cinnum(n, FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B2DC5DE653290AD56229C8BC5); // 创建曲线对象 sm2_curve ecp_init(MIRACL, p, a, b); if (!sm2_curve) { printf(ecp_init failed\n); return; } // 设置基点 G压缩格式 02 x ecp_set(Gx, Gy, sm2_curve, TRUE); // TRUE 表示已验证在曲线上 }逻辑说明cinnum()将十六进制字符串转为big类型ecp_init()构造素域椭圆曲线结构体ecp_set()将基点 G 加载进曲线上下文。参数TRUE表示跳过点有效性校验因 G 是标准值可省开销。3.2 SM2 密钥对生成私钥 d ∈ [1, n−1]公钥 Q d·G// 生成 256 位私钥 d均匀随机 void sm2_keygen(big d, ecp *Q) { big r; r mirvar(0); do { irand(r); // 使用 miracl 内置 PRNG已配置 MR_SEED_BYTES32 divide(r, n, r); // r r mod n } while (iszero(r) || isunity(r)); // d 必须 ∈ [1, n−1] copy(r, d); ecp_mult(d, sm2_curve-G, Q, sm2_curve); // Q d * G } // 示例调用 big d; ecp *Q; d mirvar(0); Q ecp_init(MIRACL, p, a, b); sm2_keygen(d, Q); // 此时 d 为私钥Q 为公钥含 x,y 坐标参数说明irand()生成 [0, 2^MR_BITS) 区间随机数divide(r,n,r)实现模约减ecp_mult()是标量乘法核心时间复杂度 O(log d)Miracl 内部采用 Montgomery ladder 防侧信道。3.3 SM2 加密ECIES 框架下的 C1||C3||C2 三段式密文SM2 加密不是简单encrypt(plaintext, pubkey)而是生成临时密钥对(k, C1k·G)计算共享密钥t k·QQ 为接收方公钥计算C2 plaintext XOR KDF(t, keylen)计算C3 Hash(x || M || y)SM3 摘要此处用 SHA-256 替代示意// 简化版加密忽略 SM3用 SHA256 代替实际项目需接入国密 SM3 实现 void sm2_encrypt(unsigned char *pt, int ptlen, ecp *Q, unsigned char *ct, int *ctlen) { big k, t_x, t_y, x, y; ecp *C1, *t_point; char hash_buf[32]; k mirvar(0); t_x mirvar(0); t_y mirvar(0); x mirvar(0); y mirvar(0); C1 ecp_init(MIRACL, p, a, b); t_point ecp_init(MIRACL, p, a, b); // 1. 生成临时私钥 k计算 C1 k·G irand(k); ecp_mult(k, sm2_curve-G, C1, sm2_curve); // 2. 计算 t k·Q → 得到共享点 t_point ecp_mult(k, Q, t_point, sm2_curve); // 3. 提取 t_point 的 x,y 坐标用于 KDF 和 Hash ecp_get(t_point, x, y); // 4. KDF(x || y, ptlen) → 生成密钥流此处用 SHA256(x||y) 截断 // 真实 SM2 使用 KDF(SHA256)需另行实现 octet_to_big(x, 32, hash_buf); // x 为 32 字节大端 octet_to_big(y, 32, hash_buf32); sha256(hash_buf, 64, hash_buf); // 输出 32 字节 xor_bytes(pt, hash_buf, ct65, ptlen); // C2 pt XOR KDF // 5. C1 编码为 04 || x || y65 字节未压缩格式 ecp_to_oct(C1, ct, 65, FALSE); // FALSE 不压缩 // 6. C3 SM3(x || pt || y)此处用 SHA256 替代 memcpy(hash_buf, ct65, ptlen); sha256(hash_buf, ptlen64, hash_buf); // x(32)pt y(32) memcpy(ct65ptlen, hash_buf, 32); // C3 32 字节 *ctlen 65 ptlen 32; // C1(65) C2(ptlen) C3(32) }关键细节ecp_to_oct(..., FALSE)输出未压缩格式04 || x || y65 字节ecp_get()提取坐标供 KDF 使用C3 计算必须包含明文pt这是 SM2 防篡改的关键设计。若用压缩公钥02/03 开头则ecp_to_oct(..., TRUE)但需同步修改 C3 计算中x的取值逻辑。4. SM2 签名与验证ECDSA 变体下的 e 和 r,s 构造SM2 签名并非直接套用 ECDSA而是引入了额外的e Hash(Z || M)步骤Z 为用户标识杂凑值再计算r (e d·s) mod n。这导致签名过程不可逆推私钥且验证需重算e。4.1 签名生成输入消息 M输出 (r,s)// 用户标识默认为 1234567812345678GB/T 32918.2-2016 规定 #define SM2_ID 1234567812345678 void sm2_sign(unsigned char *msg, int msglen, big d, unsigned char *sig, int *siglen) { big k, r, s, e, tmp, n_minus_1; ecp *kG; char z_value[32], hash_buf[32]; k mirvar(0); r mirvar(0); s mirvar(0); e mirvar(0); tmp mirvar(0); n_minus_1 mirvar(0); kG ecp_init(MIRACL, p, a, b); // 1. 计算 Z SM3(ID || a || b || Gx || Gy || px || py) → 此处用 SHA256 模拟 // 真实实现需 SM3Z 固定为 32 字节 memcpy(z_value, SM2_ID, 16); octet_to_big(a, 32, z_value16); // ... 其余参数拼接略最终得 z_value[32] // 2. e Hash(Z || msg) memcpy(hash_buf, z_value, 32); memcpy(hash_buf32, msg, msglen); sha256(hash_buf, 32msglen, hash_buf); cinnum(e, hash_buf); // e 为 256 位整数 // 3. 生成随机 k ∈ [1, n−1] do { irand(k); divide(k, n, k); } while (iszero(k)); // 4. 计算 kG (x1, y1)r (e x1) mod n ecp_mult(k, sm2_curve-G, kG, sm2_curve); ecp_get(kG, r, tmp); // r x1 add(r, e, r); divide(r, n, r); // r (e x1) mod n // 5. s k⁻¹ · (r·d k) mod n invmod(k, n, tmp); // tmp k⁻¹ mod n multiply(r, d, s); // s r·d add(s, k, s); // s r·d k divide(s, n, s); // s (r·d k) mod n multiply(tmp, s, s); // s k⁻¹·(r·d k) mod n // 6. 编码 r,s 为 ASN.1 DER 或纯字节流此处用 64 字节r(32)s(32) cotnum(r, sig, 32); cotnum(s, sig32, 32); *siglen 64; }逻辑说明add(r,e,r)实现r e x1divide(r,n,r)完成模约减invmod(k,n,tmp)计算模逆元Miracl 内部用扩展欧几里得cotnum()将big转为大端 32 字节数组。注意r必须 ≠ 0否则需重选k代码中未显式检查实际应加while (iszero(r))循环。4.2 签名验证重算 e验证 (r,s) 是否满足曲线方程int sm2_verify(unsigned char *msg, int msglen, ecp *Q, unsigned char *sig, int siglen) { big r, s, e, t, v, x1, y1; ecp *point1, *point2; char z_value[32], hash_buf[32]; if (siglen ! 64) return -1; r mirvar(0); s mirvar(0); e mirvar(0); t mirvar(0); v mirvar(0); x1 mirvar(0); y1 mirvar(0); point1 ecp_init(MIRACL, p, a, b); point2 ecp_init(MIRACL, p, a, b); // 1. 解析 r,s cinnum(r, sig); cinnum(s, sig32); // 2. 计算 e Hash(Z || msg) —— 必须与签名端完全一致 memcpy(z_value, SM2_ID, 16); // ... 同签名端拼接 Z略 memcpy(hash_buf, z_value, 32); memcpy(hash_buf32, msg, msglen); sha256(hash_buf, 32msglen, hash_buf); cinnum(e, hash_buf); // 3. 计算 t (r s) mod n add(r, s, t); divide(t, n, t); // 4. 计算 point1 s·G t·Q ecp_mult(s, sm2_curve-G, point1, sm2_curve); ecp_mult(t, Q, point2, sm2_curve); ecp_add(point1, point2, point1, sm2_curve); // 5. 提取 point1.x → v x1 mod n ecp_get(point1, x1, y1); divide(x1, n, v); // 6. 验证 v r if (compare(v, r) 0) return 1; else return 0; }关键验证逻辑SM2 验证不直接检验s·G r·Q e·G而是构造point1 s·G (rs)·Q再取其 x 坐标模n与原始r比较。这是 SM2 对 ECDSA 的关键改造提升了抗伪造能力。ecp_add()在 Miracl 中自动处理无穷远点无需额外判断。5. 在 STM32F103C8T6 上部署的内存与性能实测技巧将上述 SM2 实现跑在 20KB RAM 的 Cortex-M3 上不能只靠“编译通过”必须做三项硬性约束栈深度控制、堆内存禁用、时序可预测。5.1 栈空间压测用__attribute__((section(.stack)))隔离关键函数Miracl 的ecp_mult()默认递归调用深度达 log₂(256)8 层每层约 120 字节易溢出默认 1KB 栈。解决方案// 在 startup_stm32f103xb.s 中定义独立栈段 /* .stack section: 2KB for crypto only */ .stack 0x20000000 : AT (LOADADDR(.data) SIZEOF(.data)) { . . 0x800; /* 2KB */ __stack_crypto_start .; *(.stack.crypto) __stack_crypto_end .; } // 在 C 文件中声明函数使用该栈 void __attribute__((section(.stack.crypto), naked)) sm2_sign_stacked(...) { __asm volatile ( mov sp, #0x20000000\n\t // 切换至 crypto 栈起始地址 bl sm2_sign_body\n\t mov sp, #0x20000800\n\t // 恢复主栈假设主栈顶在 0x20000800 bx lr ); }技巧说明naked函数禁用编译器栈帧管理mov sp直接切换栈指针.stack.crypto段确保链接时不会与其他数据混叠。实测表明SM2 签名函数在 2KB 栈下稳定运行而默认栈需 ≥4KB。5.2 静态内存分配表替换所有malloc为预分配池Miracl 默认用malloc分配big对象。在裸机中必须重定向// 定义全局内存池每个 big 占 32 字节 header static unsigned char big_pool[128][64]; // 128 个 big 对象每个 64 字节含对齐 static int big_used[128] {0}; void *mr_alloc(int size) { for (int i 0; i 128; i) { if (!big_used[i]) { big_used[i] 1; return big_pool[i]; } } return NULL; // 内存耗尽 } void mr_free(void *ptr) { for (int i 0; i 128; i) { if (big_pool[i] ptr) { big_used[i] 0; return; } } } // 编译时定义 MR_ALLOCmr_alloc MR_FREEmr_free效果mirvar(0)调用mr_alloc()返回池中地址避免 heap 碎片miracl内部所有big对象均从此池分配。实测 SM2 加解密全程最多占用 18 个big对象64×181152 字节远低于 20KB RAM 预留。5.3 性能基准STM32F103C8T6 72MHz 下各操作耗时单位ms操作平均耗时关键影响因素密钥生成d,Q210 msecp_mult(d,G)占 95%d 为 256 位加密128B 明文380 msecp_mult(k,G)ecp_mult(k,Q)解密128B 密文420 msecp_mult(d,C1) SM3 计算签名256B 消息290 msecp_mult(k,G) 模逆 模乘验证256B 消息510 msecp_mult(s,G)ecp_mult(t,Q)优化提示若对性能敏感可将ecp_mult()替换为固定基点优化版本如ecp_mult2()利用 G 为标准基点的特性预计算倍点表提速约 35%。但会增加 2KB ROM 占用需权衡。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。