资讯动态

OpenSSL 国密算法实战:SM2/SM3/SM4 文档与代码资源

发布时间:2026/10/26 21:25:57 来源:尧图企业网站定制
简介这份资源面向需要在Windows平台落地国密算法的开发者与安全工程师围绕SM2、SM3、SM4三套算法提供基于OpenSSL的完整实现与VC工程示例可用于身份认证、数据加密、完整性校验及消息认证码等场景帮助读者在满足国内密码技术合规要求的同时兼顾与国际标准库的兼容。压缩包共64个文件约4.65MB以c源码与h头文件为核心配套dsp、dsw等VC工程文件、obj与pdb等编译调试产物、exe可执行程序、dll与lib动态静态库以及4份PDF技术规范文档覆盖从算法原理到工程编译的完整链路。目前已有732人学习下载。读者可借助SM2加解密与签名、SM3摘要、SM4对称加密的示例代码结合OpenSSL接口快速集成到实际项目中并通过工程文件在Visual Studio中直接构建调试是理解与应用国密算法较为实用的参考材料。1. 国密算法遇上 OpenSSL一套能直接跑通的文档与代码资源手里有一套基于 OpenSSL 实现的国密算法文档和代码这事本身就值得聊。国密算法SM2/SM3/SM4在国内金融、政务、物联网场景里已经是硬性要求但真正落到工程上很多人卡在同一个地方标准文档看得懂OpenSSL 的接口对不上。这套资源解决的正是这个断层——它不是纯理论教材也不是一个孤立的 demo而是把 SM2 签名验签、SM3 摘要、SM4 加解密在 OpenSSL 框架下的实现路径、参数配置和调用方式整理成了可复现的文档加代码。适合谁正在做国密改造的后端工程师、需要过密评的项目组、以及想搞清楚 OpenSSL 国密接口到底怎么调的人。下面按“先搞懂结构、再动手编译、然后踩坑排查、最后进阶验证”的顺序拆一遍。2. OpenSSL 国密支持现状与资源结构拆解2.1 为什么是 OpenSSL 而不是从零造轮子国密算法的底层实现并不复杂SM3 是哈希、SM4 是分组密码、SM2 是椭圆曲线公钥算法理论上自己撸一遍也能跑。但工程上没人这么干原因很实际你需要的是经过验证的、能跟现有 TLS/SSL 体系对接的实现。OpenSSL 从 1.1.1 版本开始通过 provider 机制和 engine 机制支持国密算法国内也有基于 OpenSSL 1.1.1 分支的国密改造版本。这套资源选择 OpenSSL 作为底座核心逻辑是你不需要重新发明 SM2 的曲线参数和 SM3 的压缩函数你需要的是知道 OpenSSL 暴露了哪些接口、这些接口的参数怎么填、返回值怎么判断。常见做法是直接使用 OpenSSL 的 EVP 高层接口来调用国密算法而不是去碰底层的 BN大数和 EC椭圆曲线接口。EVP 接口的好处是抽象层次高、代码量少、跟其他算法的调用方式统一。但坑也在这里——国密算法在标准 OpenSSL 里的支持程度跟改造版不一样有些函数在标准版里根本不存在你得先确认自己用的是哪个版本。2.2 资源里到底有什么这套文档和代码的结构大致分三块。第一块是算法说明文档覆盖 SM2 的密钥生成、签名、验签、加密、解密流程SM3 的输入输出规格和分组填充规则SM4 的 ECB/CBC 模式及填充方式。第二块是代码示例按算法分文件每个文件是一个独立可编译的 C 程序调用 OpenSSL 的 EVP 接口完成对应操作。第三块是编译脚本和测试向量用来验证你的环境是否配置正确。代码的组织方式我比较认可每个算法一个独立目录目录里有.c源文件、Makefile、以及一个test_vector.txt存放标准测试向量。这种结构的好处是你不需要一次性把所有东西都跑通可以一个算法一个算法地验证。SM3 最简单先跑通它建立信心SM4 次之对称加密逻辑直观SM2 最复杂涉及曲线参数和密钥格式放最后啃。2.3 环境准备与版本确认在动手之前有一件事必须先做确认你的 OpenSSL 版本和支持的算法列表。标准 OpenSSL 3.x 和国密改造版的行为差异很大不确认版本就编译后面报错会把你带偏。# 查看 OpenSSL 版本 openssl version -a # 查看是否支持 SM3 openssl list -digest-algorithms | grep -i sm3 # 查看是否支持 SM4 openssl list -cipher-algorithms | grep -i sm4 # 查看是否支持 SM2 openssl list -public-key-algorithms | grep -i sm2如果上面几条命令有输出说明你的 OpenSSL 已经具备国密能力。如果list命令报错说明版本太老低于 1.1.1需要升级或换用国密改造版。注意有些系统自带的 OpenSSL 是 1.1.1 但编译时没开国密选项这种情况下list能跑但 grep 不到 sm3/sm4需要重新编译。编译代码时Makefile 里通常需要显式链接 crypto 库gcc -o sm3_test sm3_test.c -lssl -lcrypto如果报undefined reference to EVP_sm3说明链接的 OpenSSL 库不支持 SM3不是代码写错了。这个区分很重要很多人在这里浪费时间改代码实际上是库的问题。3. SM2/SM3/SM4 的 OpenSSL 调用实操3.1 SM3 摘要最简单的入口SM3 是国密哈希算法输出 256 位摘要跟 SHA-256 同级别。用 OpenSSL 的 EVP 接口调用 SM3代码结构跟调用 SHA-256 几乎一样只需要把算法名换掉。#include openssl/evp.h #include stdio.h #include string.h int main() { // 待摘要的数据 const char *msg abc; unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len 0; // 获取 SM3 摘要算法 EVP_MD_CTX *ctx EVP_MD_CTX_new(); if (ctx NULL) { printf(EVP_MD_CTX_new failed\n); return 1; } // 初始化指定 SM3 算法 if (EVP_DigestInit_ex(ctx, EVP_sm3(), NULL) ! 1) { printf(EVP_DigestInit_ex failed, SM3 not supported?\n); EVP_MD_CTX_free(ctx); return 1; } // 更新数据 if (EVP_DigestUpdate(ctx, msg, strlen(msg)) ! 1) { printf(EVP_DigestUpdate failed\n); EVP_MD_CTX_free(ctx); return 1; } // 输出摘要 if (EVP_DigestFinal_ex(ctx, digest, digest_len) ! 1) { printf(EVP_DigestFinal_ex failed\n); EVP_MD_CTX_free(ctx); return 1; } // 打印十六进制结果 for (unsigned int i 0; i digest_len; i) { printf(%02x, digest[i]); } printf(\n); EVP_MD_CTX_free(ctx); return 0; }这段代码的逻辑很直白创建上下文、初始化指定算法、喂数据、取结果、释放上下文。关键参数是EVP_sm3()它返回 SM3 的算法描述符。如果这个函数返回 NULL 或者EVP_DigestInit_ex失败基本可以断定当前 OpenSSL 不支持 SM3。对 abc 做 SM3标准结果应该是66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0。如果你跑出来的不是这个值先检查输入数据有没有多算换行符或空格。测试向量文件里一般会给出输入和期望输出逐字节对比。3.2 SM4 加解密分组模式和填充的坑SM4 是 128 位分组密码密钥也是 128 位。OpenSSL 里通过 EVP 接口调用 SM4支持 ECB 和 CBC 模式。ECB 模式不需要 IVCBC 模式需要 16 字节 IV。#include openssl/evp.h #include stdio.h #include string.h int main() { // 16 字节密钥 unsigned char key[16] { 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10 }; // 16 字节 IVCBC 模式使用 unsigned char iv[16] { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; // 待加密明文 unsigned char plaintext[] Hello SM4!; unsigned char ciphertext[64]; unsigned char decrypted[64]; int out_len, dec_len; // 加密 EVP_CIPHER_CTX *ctx EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_sm4_cbc(), NULL, key, iv); EVP_EncryptUpdate(ctx, ciphertext, out_len, plaintext, strlen((char*)plaintext)); int total_len out_len; EVP_EncryptFinal_ex(ctx, ciphertext out_len, out_len); total_len out_len; EVP_CIPHER_CTX_free(ctx); printf(Ciphertext (%d bytes): , total_len); for (int i 0; i total_len; i) printf(%02x, ciphertext[i]); printf(\n); // 解密 ctx EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_sm4_cbc(), NULL, key, iv); EVP_DecryptUpdate(ctx, decrypted, dec_len, ciphertext, total_len); int dec_total dec_len; EVP_DecryptFinal_ex(ctx, decrypted dec_len, dec_len); dec_total dec_len; EVP_CIPHER_CTX_free(ctx); decrypted[dec_total] \0; printf(Decrypted: %s\n, decrypted); return 0; }这里有几个参数必须说清楚。EVP_sm4_cbc()指定 CBC 模式如果要 ECB 模式就换成EVP_sm4_ecb()但 ECB 模式下 IV 参数传 NULL。密钥和 IV 都必须是 16 字节少一个字节都会导致EVP_EncryptInit_ex返回失败。填充方面OpenSSL 默认使用 PKCS#7 填充。如果你跟其他系统对接对方用的是零填充或无填充就需要用EVP_CIPHER_CTX_set_padding(ctx, 0)关闭默认填充然后自己处理数据对齐。这个坑在跨系统对接时特别常见——你这边加密出来的密文长度跟对方期望的对不上八成是填充方式不一致。3.3 SM2 签名验签曲线参数和密钥格式SM2 是基于椭圆曲线的公钥算法OpenSSL 里通过 EVP 接口调用。SM2 的密钥生成、签名、验签流程比 SM3 和 SM4 复杂主要复杂在密钥格式和曲线参数的指定上。#include openssl/evp.h #include openssl/pem.h #include stdio.h int main() { // 生成 SM2 密钥对 EVP_PKEY_CTX *pctx EVP_PKEY_CTX_new_id(EVP_PKEY_SM2, NULL); EVP_PKEY_keygen_init(pctx); EVP_PKEY *pkey NULL; EVP_PKEY_keygen(pctx, pkey); EVP_PKEY_CTX_free(pctx); // 待签名数据 const char *msg sign me; unsigned char sig[256]; size_t sig_len sizeof(sig); // 签名 EVP_MD_CTX *mdctx EVP_MD_CTX_new(); EVP_PKEY_CTX *sctx NULL; EVP_DigestSignInit(mdctx, sctx, EVP_sm3(), NULL, pkey); EVP_DigestSign(mdctx, sig, sig_len, (unsigned char*)msg, strlen(msg)); EVP_MD_CTX_free(mdctx); printf(Signature length: %zu\n, sig_len); // 验签 mdctx EVP_MD_CTX_new(); EVP_PKEY_CTX *vctx NULL; EVP_DigestVerifyInit(mdctx, vctx, EVP_sm3(), NULL, pkey); int ret EVP_DigestVerify(mdctx, sig, sig_len, (unsigned char*)msg, strlen(msg)); EVP_MD_CTX_free(mdctx); printf(Verify result: %s\n, ret 1 ? OK : FAIL); EVP_PKEY_free(pkey); return 0; }这段代码里EVP_PKEY_SM2指定密钥类型为 SM2EVP_sm3()指定签名时使用的摘要算法。SM2 签名标准里默认用 SM3 做摘要所以这两个要配对使用。密钥格式是另一个容易翻车的地方。OpenSSL 生成的 SM2 密钥默认是 PEM 格式但有些系统要求裸密钥或者特定编码的密钥。如果你需要把密钥导出给其他系统用注意EVP_PKEY_get_raw_public_key和EVP_PKEY_get_raw_private_key这两个接口拿到的裸密钥格式跟标准 SM2 密钥格式可能有差异。文档里一般会给出密钥格式转换的示例代码照着改参数就行。4. 编译与运行时的避坑排查4.1 现象编译报 undefined reference原因链接阶段找不到 OpenSSL 的符号通常是 Makefile 里没加-lcrypto或者系统里装了多个 OpenSSL 版本链接到了不支持国密的那个。解决先ldd看你的可执行文件链接的是哪个 libcrypto再用openssl version确认那个库的版本。如果是多版本共存在 Makefile 里用-L/path/to/correct/openssl/lib显式指定路径。4.2 现象EVP_sm3() 返回 NULL原因当前 OpenSSL 库编译时没有启用 SM3 支持。标准 OpenSSL 1.1.1 默认是支持的但有些发行版的打包版本为了精简体积把国密算法裁掉了。解决用openssl list -digest-algorithms确认。如果没有 sm3需要自己编译 OpenSSL 并开启国密选项或者换用国密改造版。编译时加enable-sm2 enable-sm3 enable-sm4之类的配置参数具体参数名看版本。4.3 现象SM4 解密出来是乱码原因最常见的是 IV 不匹配或填充方式不一致。CBC 模式下加密和解密必须用同一个 IVIV 错了第一个分组就解不对。填充方式不一致会导致最后一个分组解出来多几个字节或少几个字节。解决先确认加密和解密用的是同一组 key 和 IV再确认两边填充方式一致。如果对方系统用的是零填充你这边要关掉 OpenSSL 的默认填充手动把数据补齐到 16 字节的整数倍。4.4 现象SM2 验签失败但签名长度正常原因SM2 签名涉及用户 ID 的默认值问题。标准 SM2 签名时有一个默认用户 ID通常是 1234567812345678如果签名和验签两边的用户 ID 不一致验签就会失败。解决检查EVP_PKEY_CTX_set1_id是否在签名和验签两边都设置了相同的 ID。如果一方没设用的就是默认值另一方设了非默认值就会对不上。4.5 现象程序运行时报 segment fault原因EVP 上下文没有正确初始化或提前释放。比如EVP_MD_CTX_new之后没有检查返回值就直接用或者EVP_CIPHER_CTX_free之后又调用了该上下文。解决每个_new后面都加 NULL 检查每个_free之后把指针置 NULL。EVP 接口的返回值检查不能省国密算法在某些 OpenSSL 版本里可能返回特定错误码不检查就会带着空指针往下跑。5. 用测试向量验证实现正确性代码能编译、能跑出结果不代表结果是对的。国密算法有标准测试向量文档里一般会附带。验证方法是用测试向量里的输入跑你的程序把输出跟期望值逐字节对比。SM3 的标准测试向量输入 abc期望输出66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0。输入为空字符串期望输出1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b。SM4 的标准测试向量密钥和明文都是0123456789abcdeffedcba9876543210ECB 模式加密后的密文是681edf34d206965e86b3e94f536e4246。这个向量可以用来验证你的 SM4 实现是否跟标准一致。SM2 的验证稍微麻烦一点因为涉及随机数。但签名验签的自洽性可以验证用同一对密钥签名再验签结果应该是 OK。如果要跟标准向量对比需要用固定随机数的签名结果文档里如果有提供就照着跑。我一般会写一个简单的 shell 脚本把所有测试向量跑一遍#!/bin/bash # 依次运行各算法测试并对比期望值 echo SM3 test ./sm3_test echo Expected: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0 echo SM4 test ./sm4_test echo Expected: 681edf34d206965e86b3e94f536e4246 echo SM2 test ./sm2_test跑完对比输出全部匹配说明环境配置和代码实现都没问题。有一个不匹配就按第 4 章的排查思路定位。从那以后我每次拿到新的国密代码包都强制先跑一遍测试向量再动业务代码。这个习惯帮我省了很多来回折腾的时间——环境问题在测试向量阶段暴露比在业务联调阶段暴露成本低得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑