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;
}
编译技巧:如果想更方便,直接用 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 的官方示例,遇到问题多跑几个测试用例。