3. OpenSSL 国密编译:从源码到生产环境
说实话,国密改造项目里最让我头疼的,反而不是算法本身,而是编译环境。OpenSSL 1.1.1 开始原生支持国密,但默认不开启。你得自己动手,把 SM2、SM3、SM4 这些模块编译进去。
我最早做国密网关的时候,就因为编译参数漏了一个,导致 SM2 签名验签一直报错。查了两天才发现是 enable-ec_nistp_64_gcc_128 和国密扩展冲突了。嗯,这些坑咱们今天一次性填平。
3.1 OpenSSL 1.1.1 国密编译实战
先明确一点:OpenSSL 1.1.1 的国密支持是实验性的。什么意思?就是功能能用,但默认不给你开。你得在 configure 阶段显式告诉它:我要国密。
核心编译参数
enable-ec_nistp_64_gcc_128— 启用 64 位优化,但和国密 EC 曲线有冲突enable-sm2— 开启 SM2 签名/加密/密钥交换enable-sm3— 开启 SM3 哈希enable-sm4— 开启 SM4 对称加密
我个人的习惯是,先做一次干净的源码解压,然后直接上配置命令。别在已经编译过的目录里反复改参数,容易出脏数据。
# 下载并解压 OpenSSL 1.1.1 系列(以 1.1.1w 为例)
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz
tar xzf openssl-1.1.1w.tar.gz
cd openssl-1.1.1w
# 配置国密支持
./Configure linux-x86_64 \
--prefix=/usr/local/ssl_gm \
--openssldir=/usr/local/ssl_gm \
enable-ec_nistp_64_gcc_128 \
enable-sm2 \
enable-sm3 \
enable-sm4 \
no-ssl2 \
no-ssl3 \
shared
# 编译 & 安装
make -j$(nproc)
make install
注意: enable-ec_nistp_64_gcc_128 和国密 SM2 曲线在部分 GCC 版本下会冲突。如果你编译时遇到 EC 曲线相关的错误,去掉这个参数再试。我在 CentOS 7 的 GCC 4.8.5 上就踩过这个坑。
编译完成后,验证一下国密算法是否真的可用:
# 查看是否支持 SM3
/usr/local/ssl_gm/bin/openssl dgst -sm3
# 查看是否支持 SM4
/usr/local/ssl_gm/bin/openssl enc -sm4-cbc
# 查看 EC 曲线列表里是否有 SM2
/usr/local/ssl_gm/bin/openssl ecparam -list_curves | grep SM2
如果三条命令都不报错,说明国密编译成功了。我曾经遇到过 SM2 曲线列表能出来,但 SM3 哈希报 unknown digest 的情况。后来发现是 make clean 没做,旧的对象文件污染了新编译。所以我的建议是:每次改配置前,先 make clean 或者直接删掉源码目录重新解压。
3.2 GMSSL:另一个选择
OpenSSL 的国密支持毕竟是后加的,有些细节不够完善。比如 SM2 的签名格式和标准不完全一致,SM4 的 GCM 模式在 1.1.1 里也没有。这时候,GMSSL 就派上用场了。
GMSSL 是北京大学开源的一个国密实现,完全遵循国标。说白了,它就是为国密而生的。如果你做的是纯国密项目,我建议直接上 GMSSL,省去很多适配的麻烦。
我的选择建议:
- 如果项目需要同时支持国际算法和国密(比如双证书体系),用 OpenSSL 1.1.1 + 国密编译
- 如果项目只做国密,或者对国密合规性要求极高,用 GMSSL
3.3 GMSSL 安装与配置
GMSSL 的编译比 OpenSSL 简单,因为它默认就开了国密。你不需要纠结哪些参数要加、哪些参数要删。
# 下载 GMSSL(以 GmSSL-3.1.1 为例)
wget https://github.com/guanzhi/GmSSL/archive/refs/tags/v3.1.1.tar.gz
tar xzf v3.1.1.tar.gz
cd GmSSL-3.1.1
# 配置 & 编译
mkdir build && cd build
cmake ..
make -j$(nproc)
make install
安装完成后,GMSSL 默认会覆盖系统里的 openssl 命令。我个人不太喜欢这样,因为可能会影响其他依赖 OpenSSL 的服务。我的做法是:
- 编译时指定
--prefix,安装到独立目录,比如/usr/local/gmssl - 用绝对路径调用 GMSSL,或者通过
alias区分
# 验证 GMSSL 国密功能
/usr/local/gmssl/bin/gmssl sm2keygen -pass 1234 -out sm2.pem
/usr/local/gmssl/bin/gmssl sm3 sm2.pem
GMSSL 3.x 版本和 OpenSSL 1.1.1 在 API 层面有差异。比如 SM2 签名,OpenSSL 用的是 EVP_PKEY_sign,GMSSL 有自己的 GmSSL_SM2_sign 接口。如果你要做接口适配,这块得单独处理。
3.4 两种方案的对比
| 对比维度 | OpenSSL 1.1.1 + 国密编译 | GMSSL |
|---|---|---|
| 国密合规性 | 部分接口与国标有偏差 | 完全遵循国标 |
| 国际算法支持 | 完整支持 RSA/ECC/AES 等 | 支持,但非核心 |
| 编译难度 | 中等,参数容易踩坑 | 简单,默认开启国密 |
| 社区活跃度 | 高,文档丰富 | 中等,中文文档为主 |
| 适用场景 | 双算法共存、国际互通 | 纯国密、合规审查 |
3.5 知识体系:国密编译选型决策流
下面这张图,是我自己总结的选型逻辑。每次做国密改造项目,我都会先过一遍这个流程,避免走弯路。
3.6 避坑指南
最后,分享几个我实际项目中遇到的坑,希望能帮你省点时间。
坑1:OpenSSL 1.1.1 的 SM2 签名格式
OpenSSL 1.1.1 的 SM2 签名输出是 DER 编码的 EVP_PKEY_SIGNATURE,而国标要求的是纯 r||s 格式(64 字节)。如果你对接的国密网关或 CA 要求纯 64 字节签名,你得自己写个转换函数。我在对接某省 CA 时就因为这个被卡了三天。
坑2:GMSSL 的 API 不兼容 OpenSSL
GMSSL 3.x 的 API 和 OpenSSL 1.1.1 不完全兼容。比如 EVP_MD_CTX_new 在 GMSSL 里是 GmSSL_MD_CTX_new。如果你从 OpenSSL 迁移到 GMSSL,别想着只改个头文件就完事。我建议你写一个抽象层,把国密调用封装起来,这样切换底层库时只需要改封装层。
坑3:动态库冲突
系统里同时装了 OpenSSL 和 GMSSL 时,LD_LIBRARY_PATH 的顺序会导致调用错库。我的做法是:在启动脚本里显式指定 LD_PRELOAD,或者在编译时把国密库静态链接进去。静态链接虽然体积大一点,但省心。