4. GmSSL 库基础:源码编译、基本 API 调用与示例程序

GmSSL 是国密算法的开源实现,覆盖了 SM2/SM3/SM4/SM9 以及 ZUC 等国标算法。说白了,它就是国密领域的 OpenSSL。我刚开始研究国密时,第一个接触的就是它。

这一章的核心就三个事:怎么编译、怎么调用、怎么跑通。你想想看,如果连编译都搞不定,后面调用 API 就是空中楼阁。好,我们直接开始。

4.1 源码编译:从下载到可执行文件

我个人习惯用构建目录来编译,这样源码目录始终保持干净,万一搞砸了,删掉 build 目录重来就行。

编译前的准备

  • 操作系统:Ubuntu 20.04 / CentOS 7 以上,macOS 也行,但 Windows 建议用 WSL
  • 工具链:gcc、cmake、make、git(确认已安装)
  • OpenSSL 开发库(某些功能依赖):sudo apt install libssl-dev

我建议你们也养成这个习惯。有次我帮同事排查编译问题,发现他直接在源码目录里跑 cmake,结果缓存冲突,折腾了一小时才找到原因。

# 克隆最新版 GmSSL
git clone https://github.com/guanzhi/GmSSL.git
cd GmSSL

# 创建构建目录
mkdir build
cd build

# 配置、编译、安装
cmake ..
make -j$(nproc)
sudo make install
编译选项 说明 推荐值
-DCMAKE_INSTALL_PREFIX 指定安装路径 /usr/local
-DBUILD_SHARED_LIBS=ON 编译动态库 ON
-DENABLE_TESTS=ON 编译测试用例 ON(调试阶段)

注意:如果编译时报错“找不到 OpenSSL 头文件”,检查一下 libssl-dev 是否装好了。我曾经遇到过这种情况,最后发现是系统里装了多个版本的 OpenSSL,cmake 找错了路径。解决办法是显式指定 -DOPENSSL_ROOT_DIR=/usr。

4.2 基本 API 调用:SM4 加解密与 SM3 哈希

编译好了,接下来就是调库。GmSSL 的 API 设计和 OpenSSL 很像,如果你用过 OpenSSL,上手会很快。没接触过也不要紧,我带着你走一遍最常见的两个场景。

4.2.1 SM4-CBC 加解密

SM4 是对称加密,用在数据传输加密、文件加密这些场景。核心结构是 GmSSL_CTX 和 GmSSL_CIPHER_CTX。我记得刚开始用 GmSSL 时,总是忘记初始化上下文。后来我总结了一个口诀:「先 init,再 set,最后 do_final」。

#include <gmssl/sm4.h>
#include <gmssl/rand.h>

void sm4_cbc_example() {
    unsigned char key[16], iv[16];
    unsigned char plaintext[] = "Hello, GmSSL!";
    unsigned char ciphertext[64], decrypted[64];
    int outlen, len;

    // 生成随机密钥和 IV
    rand_bytes(key, sizeof(key));
    rand_bytes(iv, sizeof(iv));

    SM4_KEY sm4_key;
    SM4_CTX ctx;
    sm4_set_encrypt_key(&sm4_key, key);
    sm4_cbc_init(&ctx, &sm4_key, iv, SM4_ENCRYPT);
    sm4_cbc_update(&ctx, plaintext, strlen((char*)plaintext), ciphertext, &outlen);
    sm4_cbc_final(&ctx, ciphertext + outlen, &len);
    outlen += len;

    // 解密
    sm4_set_decrypt_key(&sm4_key, key);
    sm4_cbc_init(&ctx, &sm4_key, iv, SM4_DECRYPT);
    sm4_cbc_update(&ctx, ciphertext, outlen, decrypted, &outlen);
    sm4_cbc_final(&ctx, decrypted + outlen, &len);

    printf("解密结果: %s\n", decrypted);
}

我的经验:调用 sm4_cbc_final 前,缓冲区留足空间。加密后的密文长度可能比原文多 16 字节(PKCS#7 填充)。我曾经因为缓冲区小了,导致内存越界,程序莫名其妙崩溃,查了好半天才发现。

4.2.2 SM3 哈希计算

SM3 是哈希算法,输出 256 位的摘要。用法和 SM4 一样,也有上下文模式。

#include <gmssl/sm3.h>

void sm3_example() {
    unsigned char digest[32];
    SM3_CTX ctx;
    const char *msg = "国密算法";

    sm3_init(&ctx);
    sm3_update(&ctx, msg, strlen(msg));
    sm3_final(&ctx, digest);

    // 打印十六进制摘要
    for (int i = 0; i < 32; i++)
        printf("%02x", digest[i]);
    printf("\n");
}

嗯,这里要注意:SM3 的输出是 32 字节,不是 64 也不是 128。有次我同事把 SM3 和 SM2 签名搞混了,结果验签总失败。说白了,SM3 就是取摘要,SM2 签名才用到椭圆曲线,两码事。

4.3 示例程序:完整的 SM2 签名验证流程

SM2 是国密非对称算法,用于数字签名和密钥交换。下面我们写一个完整的示例:生成密钥对 → 签名 → 验签。为什么要强调完整?因为我在项目里见过太多人只测试了「正常路径」,上生产后才发现公私钥格式不对。

#include <gmssl/sm2.h>
#include <gmssl/rand.h>
#include <string.h>

int main() {
    SM2_KEY keypair;
    SM2_SIGN_CTX sign_ctx;
    unsigned char msg[] = "国密签名测试";
    unsigned char sig[512];
    size_t siglen = sizeof(sig);
    int ret;

    // 1. 生成密钥对
    sm2_key_generate(&keypair);
    printf("私钥: ");
    sm2_private_key_info_print(stdout, 0, 0, &keypair, 0);
    printf("\n公钥: ");
    sm2_public_key_info_print(stdout, 0, 0, &keypair, 0);

    // 2. 签名
    sm2_sign_init(&sign_ctx, &keypair, SM2_DEFAULT_ID);
    sm2_sign_update(&sign_ctx, msg, strlen((char*)msg));
    sm2_sign_finish(&sign_ctx, sig, &siglen);
    printf("\n签名长度: %zu 字节\n", siglen);

    // 3. 验证签名
    SM2_SIGN_CTX verify_ctx;
    sm2_verify_init(&verify_ctx, &keypair, SM2_DEFAULT_ID);
    sm2_verify_update(&verify_ctx, msg, strlen((char*)msg));
    ret = sm2_verify_finish(&verify_ctx, sig, siglen);
    if (ret == 1) {
        printf("签名验证成功!\n");
    } else {
        printf("签名验证失败!\n");
    }

    sm2_key_free(&keypair);
    return 0;
}
GmSSL 核心调用流程 GmSSL 核心调用流程 获取源码 编译安装 SM4 对称加解密 sm4_set_encrypt_key SM3 哈希 sm3_update / sm3_final SM2 非对称 签名 / 验签 验证与测试 交叉验证、性能测试 按流程依次验证,确保每步输出正确后再进入性能测试

编译技巧:如果想更方便,直接用 gcc -o test_sm2 test_sm2.c -lgmssl -L/usr/local/lib 编译。不过我建议你写 CMakeLists.txt,后面集成到项目里也省事。

4.4 避坑指南:编译与调用常见问题

  • 运行时找不到动态库:设置 export LD_LIBRARY_PATH=/usr/local/lib,或者把库路径写入 /etc/ld.so.conf.d/。
  • 头文件冲突:如果系统里同时装了 OpenSSL 和 GmSSL,记得在 #include 时显式指定路径,比如 #include <gmssl/sm4.h> 而不是 #include <sm4.h>。
  • 签名结果缓冲区不足:SM2 签名的最大长度约为 512 字节,但保险起见,我一般用 unsigned char sig[1024],宁可多分配也不要少。
  • 跨平台字节序:SM4 的密钥和 IV 在不同架构间传输时,记得统一为大端序。我曾经在 ARM 和 x86 之间传输密文,发现解密失败,最后查出来是字节序没转。

最后一次提醒:测试 SM2 签名时,一定要使用 相同的默认用户 ID(SM2_DEFAULT_ID)。如果签名和验签用了不同的 ID,结果肯定不对。这个坑我踩过不止一次,希望你一次都不要踩。

好,这一章的内容就这么多。你只要走通了编译、调通了 SM4/SM3/SM2 这三个核心 API,后面国密适配基本上就没什么障碍了。剩下的就是多看 GmSSL 的官方示例,遇到问题多跑几个测试用例。