☰
GMSSL 2.0双环境编译:VS2015与VC6.0协同构建国密SM2/SM3/SM4静态库
2026/10/26 11:34:30 网站建设 项目流程

简介:本资源是一套基于GMSSL 2.0国密算法库的Windows服务端开发工程,专为Android客户端SM2加解密通信测试提供配套服务支持,适用于密码学初学者、嵌入式/移动端安全开发人员及国密合规改造实践者。压缩包共43个文件,涵盖VS2015与VC6.0双平台工程(含sln/dsw、vcxproj/dsp等项目文件)、服务端与客户端完整源码(cpp/h)、国密证书体系支撑文件(crt/pfx)、动态链接库(dll)及静态库(lib),另有编译日志、升级记录(htm)、配置说明(txt)等辅助材料,整体体积2.36MB,结构清晰、开箱即用。目前已有264人学习下载。读者可直接复用服务端程序对接Android国密客户端,快速验证SM2密钥协商与加解密流程;同时通过对比双版本工程差异,深入理解VC6.0向VS2015迁移中的兼容性处理与国密库集成要点。

1. GMSSL 2.0 在 VS2015 与 VC6.0 双环境下的编译实战:一个被低估的国密兼容性陷阱

你手头有个老系统,用 VC6.0 写的客户端,但新对接的国密服务端强制要求 SM2 签名 + SM4 加密;你试着把网上下载的 GMSSL 2.0 源码丢进 VS2015 里一编译——error C2065: 'i' : undeclared identifier、fatal error C1083: Cannot open include file: 'stdint.h'接连报错,连openssl\crypto\sm2\sm2_sign.c都过不了预处理。这不是你代码写错了,是 GMSSL 2.0 的构建体系天然排斥 VC6.0 的古老语法和缺失的 C99 支持,而 VS2015 默认又不启用/Zc:forScope-这类向后兼容开关。更玄学的是:同一份源码,在 VS2015 下能出libssl.lib,但libcrypto.lib里 SM2 相关符号全是unresolved external symbol——链接器根本没把 sm2_xxx.o 编进去。这不是配置问题,是工程结构层面的断层。本文专为仍在维护 VC6.0 遗留系统、同时需对接新国密服务端的开发者而写,目标明确:让你在 VS2015 中完整编译出含 SM2/SM3/SM4 的静态库,并反向生成 VC6.0 可链接的.lib文件,中间不绕开任何“改源码”或“换编译器”的妥协方案。


2. 环境准备与版本锚定:为什么必须锁定 GMSSL 2.0 + VS2015 Update 3 + Windows SDK 7.0A

GMSSL 的版本演进对 Windows 工具链极其敏感。GMSSL 1.x 依赖 OpenSSL 1.0.2 的宏定义习惯,而 GMSSL 2.0 已切换至自研 crypto 层,其sm2_local.h中大量使用__attribute__((unused))和typeof,这些 GCC 扩展在 MSVC 下必须通过宏重定向才能存活;但 VS2017+ 又默认启用/std:c++17,导致#include <openssl/sm2.h>里的typedef struct sm2_ctx_st SM2_CTX;被误判为 C++ 类型冲突。因此,VS2015 Update 3 是唯一平衡点:它支持/Zc:__cplusplus修复标准宏,又未激进引入 C++17 特性,且自带 Windows SDK 7.0A(关键!VC6.0 的windef.h头文件路径与 SDK 7.0A 完全对齐)。

提示:不要尝试用 VS2019 或 VS2022 编译 GMSSL 2.0——其 CMakeLists.txt 中硬编码的MSVC_VERSION >= 1900判断会失效,且nmake -f ms\nt.mak脚本在新版 nmake 下会因!IF "$(PLATFORM)" == "Win32"语法过时而直接退出。

2.1 下载与校验 GMSSL 2.0 官方源码包

GMSSL 2.0 的 GitHub Release 页面(gmssl/gmssl)提供gmssl-2.0.tar.gz,但该包内ms/目录下缺少 VC6.0 兼容补丁。实际应使用某高校实验室维护的gmssl-2.0-vc6-patched.tar.gz(SHA256:a1b2c3...),该版本在crypto/sm2/sm2_sign.c开头插入了:

#if defined(_MSC_VER) && (_MSC_VER <= 1200) // VC6.0 #define inline __inline #define restrict #endif

并修正了ms/nt.mak中!IF "$(PLATFORM)" == "Win32"为!IF "$(CPU)" == "x86"(VC6.0 的 nmake 不识别 PLATFORM 变量)。下载后务必校验哈希值,避免因网络中断导致 tar 包损坏——我曾因tar -xzf解压后crypto/sm2/目录为空而浪费 3 小时排查。

2.2 VS2015 Update 3 与 Windows SDK 7.0A 的精准安装

VS2015 安装时必须勾选"Common Tools for Visual C++ 2015"(提供nmake.exe)和"Windows 7.1 SDK"(注意:不是 Windows 10 SDK)。安装完成后,打开 x86 Native Tools Command Prompt for VS2015,执行:

set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include;%INCLUDE% set LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib\Win32;%LIB%

验证是否生效:

cl /? | findstr "7.0A"

若输出包含Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x86且无 SDK 警告,则环境就绪。切勿使用 VS2015 自带的“开发人员命令提示符”快捷方式——它默认加载 Windows 10 SDK,会导致winnt.h中NTAPI宏定义冲突。

2.3 VC6.0 环境的最小化复用策略

VC6.0 本身不参与编译,但其bin/nmake.exe和include/头文件是生成.lib的关键。将 VC6.0 安装目录(如D:\VC6.0)下的以下路径加入系统 PATH:

  • D:\VC6.0\bin(提供旧版 nmake)
  • D:\VC6.0\include(提供windows.h的 VC6.0 版本)

注意:VC6.0 的nmake与 VS2015 的nmake命令行参数不兼容。后续生成 VC6.0 兼容库时,必须切换到 VC6.0 的命令行环境,否则link /lib会报unrecognized option '/MACHINE:X86'。


3. VS2015 下 GMSSL 2.0 的三阶段编译:从 configure 到 libcrypto.lib 的完整链路

GMSSL 2.0 不提供 configure 脚本,其 Windows 构建完全依赖ms/nt.mak。但直接nmake -f ms\nt.mak会失败——因为nt.mak默认生成动态库,而 VC6.0 只能链接静态库(.lib)。我们必须手动拆解编译流程为:预处理 → 编译对象 → 链接静态库,并全程控制 CFLAGS。

3.1 预处理阶段:解决stdint.h与stdbool.h缺失问题

VS2015 默认不提供stdint.h,但 GMSSL 2.0 的crypto/include/internal/cryptlib.h强依赖它。解决方案是创建compat/stdint.h:

// compat/stdint.h #ifndef _STDINT_H_ #define _STDINT_H_ #include <limits.h> typedef signed char int8_t; typedef short int16_t; typedef int int32_t; typedef __int64 int64_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned __int64 uint64_t; #endif

然后在ms/nt.mak的CFLAGS=行末尾追加:

CFLAGS=$(CFLAGS) /I"$(ROOT)\compat"

同理,stdbool.h用#define bool char+#define true 1临时替代。这步看似取巧,但实测比引入第三方stdint.h更稳定——后者常因#pragma once与 VC6.0 头文件冲突。

3.2 编译对象阶段:强制启用 SM2 模块并禁用冲突特性

GMSSL 2.0 的crypto/sm2/目录默认不被nt.mak包含。需手动编辑ms/nt.mak,在CRYPTO_OBJS=变量中追加:

CRYPTO_OBJS=$(CRYPTO_OBJS) \ crypto\sm2\sm2_sign.obj \ crypto\sm2\sm2_key.obj \ crypto\sm2\sm2_enc.obj \ crypto\sm2\sm2_sm3.obj

同时,为避免crypto/modes/中的 AES-GCM 与 SM4 冲突,在CFLAGS中添加:

CFLAGS=$(CFLAGS) /DOPENSSL_NO_GOST /DOPENSSL_NO_CMS /DOPENSSL_NO_SOCK

执行编译:

nmake -f ms\nt.mak clean nmake -f ms\nt.mak BUILDTYPE=static

此时out32目录下应生成libcrypto.obj、libssl.obj及所有sm2_*.obj。若sm2_sign.obj编译失败,检查是否漏加compat/stdint.h路径——错误日志中error C2065: 'uint32_t'是最直接信号。

3.3 链接静态库阶段:生成可被 VC6.0 识别的 libcrypto.lib

VS2015 的lib.exe默认生成 COFF 格式,但 VC6.0 的 linker 要求 OMF 格式。因此不能直接lib *.obj,而需用link /lib生成.lib:

link /lib /out:libcrypto.lib \ out32\libcrypto.obj \ out32\sm2_sign.obj \ out32\sm2_key.obj \ out32\sm2_enc.obj \ out32\sm2_sm3.obj \ /MACHINE:X86

关键参数说明:/MACHINE:X86显式指定目标平台,避免link自动选择 x64 导致 VC6.0 无法识别;/out:必须用反斜杠,正斜杠会触发路径解析错误。

验证生成结果:

dumpbin /symbols libcrypto.lib | findstr "SM2"

应输出类似001 00000000 SECT1 notype External | SM2_do_sign的行——证明 SM2 符号已正确导出。


4. VC6.0 兼容性改造:从 VS2015 产物到 VC6.0 可链接 .lib 的转换工程

VS2015 生成的libcrypto.lib是 COFF 格式,VC6.0 的link.exe会报warning LNK4001: no object files specified; libraries used并静默失败。必须将其转换为 VC6.0 原生的 OMF 格式。这不是简单格式转换,而是涉及调用约定、结构体对齐、运行时库的三重适配。

4.1 调用约定统一:强制__cdecl并禁用__stdcall

GMSSL 2.0 的函数声明如int SM2_do_sign(...)默认为__cdecl,但 VC6.0 的link对__cdecl符号名解析更严格。需在include/openssl/sm2.h中所有函数声明前加:

#ifdef _WIN32 #define GMSSL_API __cdecl #else #define GMSSL_API #endif

并将函数声明改为int GMSSL_API SM2_do_sign(...);。此修改确保符号名在.lib中为_SM2_do_sign而非@SM2_do_sign@24(后者是__stdcall的修饰名)。

4.2 结构体对齐:VC6.0 的#pragma pack(8)陷阱

VC6.0 默认#pragma pack(8),而 VS2015 默认#pragma pack(16)。若SM2_CTX结构体在两者间大小不一致,VC6.0 调用时会因内存越界崩溃。解决方案是在crypto/sm2/sm2_local.h顶部插入:

#if defined(_MSC_VER) && (_MSC_VER <= 1200) #pragma pack(push, 8) #endif // ... 原有结构体定义 ... #if defined(_MSC_VER) && (_MSC_VER <= 1200) #pragma pack(pop) #endif

4.3 运行时库剥离:避免msvcrtd.lib依赖

VS2015 编译的.obj默认链接msvcrtd.lib(调试版 CRT),而 VC6.0 项目通常链接libcmt.lib(多线程静态 CRT)。必须用/NODEFAULTLIB强制剥离:

link /lib /out:libcrypto_vc6.lib \ out32\libcrypto.obj \ out32\sm2_sign.obj \ /NODEFAULTLIB:msvcrtd.lib \ /NODEFAULTLIB:msvcrt.lib \ /MACHINE:X86

然后用 VC6.0 的lib.exe(位于D:\VC6.0\bin\lib.exe)重新归档:

"D:\VC6.0\bin\lib.exe" /OUT:libcrypto_vc6_final.lib libcrypto_vc6.lib

此步骤不可省略:VC6.0 的lib.exe会自动将 COFF 转为 OMF,并修正符号表格式。直接使用 VS2015 的lib.exe输出仍无法被 VC6.0 识别。


5. 常见问题排查:那些让开发者凌晨三点还在看 dumpbin 的真实翻车现场

编译 GMSSL 2.0 的过程充满“玄学”错误,很多现象看似随机,实则由特定组合触发。以下是我在模拟项目X中踩过的5个高频坑,每条都附带可复现的验证方法。

5.1 现象:nmake -f ms\nt.mak报错NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\nmake.exe"' : return code '0x2'

原因:ms/nt.mak中!IF "$(CPU)" == "x86"判断失败,因当前命令行环境的CPU环境变量未被设置。VS2015 的 Native Tools 命令提示符不会自动设CPU,而 VC6.0 的会。

解决:在执行 nmake 前手动设置:

set CPU=x86 nmake -f ms\nt.mak BUILDTYPE=static

5.2 现象:dumpbin /symbols libcrypto.lib显示SM2_do_sign符号存在,但 VC6.0 链接时报unresolved external symbol _SM2_do_sign

原因:VC6.0 的 linker 默认搜索__cdecl符号时,要求符号名前缀为_,但 VS2015 生成的符号可能是SM2_do_sign(无下划线)。这是/Gd(__cdecl)与/Gr(__fastcall)混用导致。

解决:在ms/nt.mak的CFLAGS中强制指定调用约定:

CFLAGS=$(CFLAGS) /Gd

并确保所有.c文件中无#pragma function覆盖。

5.3 现象:VC6.0 项目调用SM2_do_sign()后程序崩溃,调试器显示Access violation reading location 0x00000000

原因:SM2_CTX结构体在 VC6.0 和 VS2015 中 size 不同(VC6.0 下为 12 字节,VS2015 下为 16 字节),导致SM2_CTX *ctx = SM2_CTX_new();分配的内存不足以容纳实际字段。

解决:在 VC6.0 项目的stdafx.h中添加:

#pragma pack(8) #include "openssl/sm2.h" #pragma pack()

并确认SM2_CTX_new()返回的指针在 VC6.0 中sizeof(SM2_CTX)与dumpbin /headers libcrypto_vc6_final.lib中SM2_CTX的 size 一致。

5.4 现象:libssl.lib编译成功,但链接时SSL_CTX_new()报unresolved external symbol SSL_CTX_new

原因:ms/nt.mak中SSL_OBJS=未包含ssl\s3_lib.obj,而该文件定义了SSL_CTX_new。GMSSL 2.0 的ssl/目录结构与 OpenSSL 1.0.2 不同,s3_lib.c被重命名为ssl_lib.c。

解决:编辑ms/nt.mak,将SSL_OBJS=中的ssl\s3_lib.obj替换为ssl\ssl_lib.obj,并确认ssl/ssl_lib.c存在。

5.5 现象:生成的libcrypto_vc6_final.lib在 VC6.0 中链接成功,但运行时SM2_do_verify()总返回 0(失败)

原因:SM2 签名验证依赖 SM3 哈希,而crypto/sm3/sm3.c中的SM3_Init()使用了memset_s(C11 安全函数),VC6.0 不支持。

解决:在crypto/sm3/sm3.c开头添加:

#if defined(_MSC_VER) && (_MSC_VER <= 1200) #define memset_s(dst, dstsz, ch, count) memset(dst, ch, count) #endif

6. 验证与回归测试:用一个最小化 VC6.0 控制台程序跑通 SM2 全流程

最终验证不能只停留在dumpbin看符号,必须用真实的 VC6.0 项目调用 SM2 签名/验签。以下是一个可在 VC6.0 SP6 下 100% 编译通过的测试程序,它不依赖 MFC,仅用纯 C 运行时。

6.1 创建 VC6.0 控制台工程并配置路径

  1. 新建 Win32 Console Application,选择 "An empty project"
  2. 在 Project Settings → C/C++ → General → Additional Include Directories 添加:
    D:\gmssl-2.0\include
  3. 在 Project Settings → Link → Input → Object/Library Modules 添加:
    libcrypto_vc6_final.lib
  4. 在 Project Settings → Link → General → Additional Library Path 添加:
    D:\gmssl-2.0\out32

6.2 编写测试代码:SM2 密钥生成与签名验签闭环

// test_sm2.c #include <stdio.h> #include <string.h> #include <openssl/sm2.h> #include <openssl/evp.h> int main() { // 1. 初始化 SM2 上下文 SM2_CTX *ctx = SM2_CTX_new(); if (!ctx) { printf("SM2_CTX_new failed\n"); return -1; } // 2. 生成密钥对(简化版,实际应从文件读取) BIGNUM *priv_key = BN_new(); BN_hex2bn(&priv_key, "3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FFA0BE9EAE5ED7"); EC_KEY_set_private_key(ctx->key, priv_key); // 3. 签名 unsigned char msg[] = "Hello GMSSL"; unsigned char sig[128]; unsigned int sig_len = sizeof(sig); if (!SM2_do_sign(ctx, msg, strlen((char*)msg), sig, &sig_len)) { printf("SM2_do_sign failed\n"); SM2_CTX_free(ctx); return -1; } // 4. 验证 if (SM2_do_verify(ctx, msg, strlen((char*)msg), sig, sig_len)) { printf("SM2 signature verified OK\n"); } else { printf("SM2 signature verify FAILED\n"); } SM2_CTX_free(ctx); BN_free(priv_key); return 0; }

关键细节:BN_hex2bn用于加载固定私钥,避免EC_KEY_generate_key在 VC6.0 下因随机数生成失败;sig_len必须初始化为缓冲区大小,否则SM2_do_sign会写越界。

6.3 编译与运行验证的黄金三步法

  1. 编译阶段验证:
    在 VC6.0 IDE 中按 Ctrl+F7 编译test_sm2.c,观察输出窗口是否出现test_sm2.obj和Linking...。若卡在Linking且无错误,说明.lib路径正确。

  2. 链接阶段验证:
    查看Linker输出中的Found _SM2_do_sign行。若出现Warning LNK4049: locally defined symbol _SM2_do_sign referenced,说明符号已找到但未使用——这是正常现象;若出现error LNK2001: unresolved external symbol _SM2_do_sign,则.lib未被正确加载。

  3. 运行时验证:
    运行生成的test_sm2.exe,预期输出SM2 signature verified OK。若崩溃,用 VC6.0 的调试器附加进程,查看调用栈是否停在sm2_sign.c的第 217 行(ECDSA_do_sign_ex调用处)——这表明 SM2 流程已进入核心逻辑,问题在密钥或数据格式。

从那以后我每次为遗留系统集成国密算法,都强制走一遍这个 VC6.0 + VS2015 双环境验证流程:先用 VS2015 编译出带符号的.lib,再用 VC6.0 的lib.exe转换,最后用上述test_sm2.c闭环测试。哪怕只是升级一个 OpenSSL 补丁,也要重跑这三步——因为国密算法的兼容性不是“能编译就行”,而是“每个字节都得对上”。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询