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 知识体系:国密编译选型决策流

下面这张图,是我自己总结的选型逻辑。每次做国密改造项目,我都会先过一遍这个流程,避免走弯路。

国密编译选型决策流程 项目启动:需要国密 是否需要同时支持国际算法? 是 否 OpenSSL 1.1.1 + 国密编译 GMSSL 配置 enable-sm2/sm3/sm4 验证 SM2/SM3/SM4 可用 cmake && make && make install 验证 SM2 密钥生成 & SM3 哈希 进入证书配置阶段

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,或者在编译时把国密库静态链接进去。静态链接虽然体积大一点,但省心。