国密 HTTPS 踩坑实录:从编译到上线的 12 个血泪教训

实践教程 · 2026-06-03 · 9 阅读

前言

国密 HTTPS 部署的技术路线并不复杂——编译 GmSSL、编译 Nginx、生成证书、配置上线。但实际操作中,每个环节都有隐藏的陷阱。本文记录了我们在生产环境中遇到的 12 个真实问题,每个坑都按"现象 → 原因 → 排查 → 解决 → 验证"的完整链路展开。

本文基于以下环境:

  • Ubuntu 22.04 LTS
  • GmSSL 3.1.1
  • Nginx 1.24.0
  • 国密双证书方案(SM2 + RSA)

坑 #1:GmSSL 编译时 OpenSSL 头文件冲突

现象: 编译 GmSSL 时报错 openssl/evp.h: No such file of directory,或者编译通过但链接时出现符号冲突。

原因: 系统已安装了 OpenSSL 开发包(libssl-dev),GmSSL 的头文件与 OpenSSL 的头文件路径冲突。GmSSL 虽然基于 OpenSSL 代码,但它是独立的分支,不能与系统 OpenSSL 混用。

排查:

BASH
# 检查系统 OpenSSL 头文件位置
dpkg -L libssl-dev | grep evp.h
# 检查是否同时存在两个 OpenSSL
openssl version
/usr/local/ssl/bin/openssl version

解决:

BASH
# 方案 A:卸载系统 OpenSSL 开发包(推荐在隔离环境中)
sudo apt remove libssl-dev

# 方案 B:编译时显式指定 GmSSL 路径
./config --prefix=/usr/local/gmssl --openssldir=/usr/local/gmssl/ssl
make && sudo make install

# 确保后续编译 Nginx 时使用 GmSSL 路径
export PKG_CONFIG_PATH=/usr/local/gmssl/lib/pkgconfig

验证:

BASH
/usr/local/gmssl/bin/openssl version
# 应输出 GmSSL 版本信息,而非系统 OpenSSL

坑 #2:Nginx 编译时找不到 GmSSL 的 SM2 实现

现象: Nginx 编译通过,但启动时报错 SSL_CTX_use_certificate: ee key too smallunsupported certificate

原因: Nginx 的 ngx_http_ssl_module 默认使用 OpenSSL 的证书验证逻辑,对 SM2 证书的 OID 识别不完整。GmSSL 3.x 将国密算法实现为 Provider,但 Nginx 可能仍链接到系统 OpenSSL 的 libcrypto。

排查:

BASH
# 检查 Nginx 链接的 SSL 库
ldd /usr/local/nginx/sbin/nginx | grep ssl
ldd /usr/local/nginx/sbin/nginx | grep crypto

# 如果输出是 /usr/lib/x86_64-linux-gnu/libssl.so,说明链接了系统 OpenSSL

解决:

BASH
# 重新编译 Nginx,强制链接 GmSSL
./configure \
    --with-http_ssl_module \
    --with-openssl=/usr/local/src/GmSSL-3.1.1 \
    --with-cc-opt="-I/usr/local/gmssl/include" \
    --with-ld-opt="-L/usr/local/gmssl/lib -Wl,-rpath,/usr/local/gmssl/lib"

make && sudo make install

验证:

BASH
ldd /usr/local/nginx/sbin/nginx | grep ssl
# 应输出 /usr/local/gmssl/lib/libssl.so

坑 #3:SM2 证书链验证失败——证书链顺序错误

现象: 浏览器或 curl 报 SSL certificate problem: unable to get local issuer certificate

原因: SM2 证书链的拼接顺序与 RSA 不同。GmSSL 生成的证书链文件(CA bundle)需要按"终端证书 → 中间 CA → 根 CA"的顺序拼接,但很多人按 RSA 的习惯只放中间 CA。

排查:

BASH
# 检查证书链内容
openssl crl2pkcs7 -nocrl -certfile /path/to/fullchain.pem | openssl pkcs7 -print_certs -noout

# 验证证书链
/usr/local/gmssl/bin/openssl verify -CAfile root-ca.pem -untrusted intermediate-ca.pem server-cert.pem

解决:

BASH
# 正确的证书链拼接顺序
cat server-cert.pem intermediate-ca.pem root-ca.pem > fullchain.pem

# Nginx 配置
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/server-key.pem;

验证:

BASH
curl -v --cacert root-ca.pem https://your-domain.com 2>&1 | grep "SSL certificate verify"
# 应输出 "SSL certificate verify ok"

坑 #4:GmSSL 生成的 SM2 证书在 Chrome 中不被信任

现象: Chrome 显示 NET::ERR_CERT_AUTHORITY_INVALID,即使证书链完整。

原因: Chrome 不支持 SM2 证书(截至 2026 年 6 月)。这不是配置问题,而是浏览器本身不支持国密算法。

解决: 双证书方案。同时部署 SM2 证书和 RSA 证书,Nginx 根据客户端能力自动选择。

注意: Nginx 1.24+ 支持多证书配置,但需要编译时启用 ssl_certificate 多证书支持。旧版本需要使用 ssl_certificate 指令的变量形式。
验证:
BASH
# 国密浏览器(360 安全浏览器)访问应正常
# Chrome 访问应自动降级到 RSA 证书
curl -v https://your-domain.com 2>&1 | grep "Server certificate"

坑 #5:Python requests 库无法验证 SM2 证书

现象: Python 脚本报 SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

原因: Python 的 ssl 模块基于系统 OpenSSL,不支持 SM2 证书验证。即使安装了 certifi 证书包,也不包含国密 CA 根证书。

排查:

PYTHON
import ssl
print(ssl.OPENSSL_VERSION)
# 如果是系统 OpenSSL,不支持 SM2

解决:

验证:

PYTHON
import requests
r = requests.get('https://your-domain.com', verify='/path/to/gm-ca-bundle.crt')
print(r.status_code)  # 应输出 200

坑 #6:MySQL 国密连接——客户端不支持 SM2 证书

现象: 应用连接 MySQL 时报 SSL connection error: protocol version mismatchcertificate verify failed

原因: MySQL 客户端(mysql-client 或各语言的 MySQL 驱动)通常基于系统 OpenSSL,不支持 SM2 证书的 TLS 连接。

排查:

BASH
# 检查 MySQL 客户端 SSL 库
ldd /usr/bin/mysql | grep ssl

# 测试 SSL 连接
mysql --ssl-mode=REQUIRED -h your-mysql-host -u user -p -e "STATUS;" | grep SSL

解决:

坑 #7:CDN 不支持国密 TLS

现象: 接入 CDN 后,国密 HTTPS 访问失败,或 CDN 回源时使用 RSA 证书。

原因: 大多数 CDN 厂商(阿里云、腾讯云、Cloudflare 等)的边缘节点不支持国密 TLS 协议。CDN 会在边缘终止 TLS,然后用普通 HTTPS 回源。

排查:

BASH
# 检查 CDN 返回的证书
curl -v https://your-domain.com 2>&1 | grep "Server certificate"
# 如果看到 RSA 证书,说明 CDN 做了 SSL 卸载

解决:

BASH
# 方案 A:CDN 使用 RSA 证书,源站同时支持国密
# CDN 配置:上传 RSA 证书
# 源站配置:同时支持 SM2 和 RSA

# 方案 B:国密用户直接访问源站(绕过 CDN)
# 通过 DNS 分流:国密浏览器 → 源站 IP,普通浏览器 → CDN CNAME

# 方案 C:使用支持国密的 CDN 厂商
# 目前国内部分政务云 CDN 支持国密,需单独确认

坑 #8:GmSSL 与系统 OpenSSL 的 PATH 冲突

现象: 编译安装 GmSSL 后,系统 openssl 命令输出 GmSSL 版本,导致其他依赖 OpenSSL 的软件异常。

原因: GmSSL 安装时覆盖了系统的 /usr/bin/openssl 或修改了 LD_LIBRARY_PATH

排查:

BASH
which openssl
openssl version
# 如果输出 GmSSL,说明系统 OpenSSL 被覆盖

解决:

BASH
# 方案 A:GmSSL 安装到独立前缀(推荐)
./config --prefix=/usr/local/gmssl --openssldir=/usr/local/gmssl/ssl
make && sudo make install
# 不要将 /usr/local/gmssl/bin 加入系统 PATH

# 方案 B:使用 update-alternatives 管理
sudo update-alternatives --install /usr/bin/openssl openssl /usr/bin/openssl 100
sudo update-alternatives --install /usr/bin/openssl openssl /usr/local/gmssl/bin/openssl 50
sudo update-alternatives --config openssl  # 手动选择

坑 #9:SM2 证书的 Key Usage 扩展缺失

现象: GmSSL 生成的 SM2 证书在 TLS 握手时报 SSL routines:tls_process_ske_sm2:bad certificate

原因: SM2 证书需要设置正确的 Key Usage 和 Extended Key Usage 扩展。GmSSL 默认生成的证书可能缺少 digitalSignaturekeyAgreement 标记。

排查:

BASH
/usr/local/gmssl/bin/openssl x509 -in server-cert.pem -text -noout | grep -A2 "Key Usage"

解决:

坑 #10:Nginx 国密 TLS 性能远低于预期

现象: 国密 HTTPS 的 QPS 只有 RSA 的 1/5,延迟高出 3 倍。

原因: SM2 签名验证的计算开销远高于 RSA-2048。GmSSL 的软件实现没有针对 x86 架构做充分的 SIMD 优化。

排查:

BASH
# 使用 openssl speed 测试
/usr/local/gmssl/bin/openssl speed sm2
openssl speed rsa2048

# 使用 wrk 压测
wrk -t4 -c100 -d30s https://your-domain.com

解决:

NGINX
# Nginx 优化配置
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# 优先使用 ECDHE-SM2 密钥交换(比纯 SM2 快)
ssl_ciphers "ECDHE-SM2-SM4-SM3:ECDHE-RSA-AES256-GCM-SHA384";
ssl_prefer_server_ciphers on;

# 启用 OCSP Stapling(减少客户端验证开销)
ssl_stapling on;
ssl_stapling_verify on;

进阶优化:

  • 使用支持国密硬件加速的密码机或 HSM
  • 考虑使用 Intel QAT 加速卡(需要 GmSSL 的 QAT 引擎支持)
  • 在负载均衡层做 SSL 卸载,后端使用明文通信

坑 #11:国密 TLS 握手失败——密码套件不匹配

现象: 客户端报 SSL routines:ssl_choose_client_version:no suitable protocolno shared cipher

原因: 客户端和服务端没有共同的国密密码套件。GmSSL 默认启用的套件与 Nginx 配置的不一致。

排查:

BASH
# 查看 GmSSL 支持的密码套件
/usr/local/gmssl/bin/openssl ciphers -v | grep SM

# 测试连接
/usr/local/gmssl/bin/openssl s_client -connect your-domain.com:443 -cipher "ECDHE-SM2-SM4-SM3"

解决:

NGINX
# Nginx 配置国密密码套件
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "ECDHE-SM2-SM4-SM3:ECC-SM2-SM4-SM3:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384";
ssl_prefer_server_ciphers on;

坑 #12:双证书方案下 OCSP Stapling 配置错误

现象: 启用 OCSP Stapling 后,Nginx 报错 SSL: cannot get OCSP responseOCSP_basic_verify failed

原因: 双证书方案中,Nginx 需要为两套证书分别配置 OCSP Stapling,但大多数配置只配了一套。

排查:

BASH
# 检查 OCSP 响应
/usr/local/gmssl/bin/openssl ocsp -issuer intermediate-ca.pem -cert server-cert.pem -url http://ocsp.your-ca.com -resp_text

解决:

注意: 如果 CA 的 OCSP 响应器不支持国密签名,OCSP Stapling 可能失败。此时可以暂时关闭 ssl_stapling_verify(不推荐生产环境),或联系 CA 确认 OCSP 响应器的国密支持情况。

检查清单

完成国密 HTTPS 部署后,按以下清单逐项验证:

总结

国密 HTTPS 部署的坑主要集中在三个层面:

  • 编译层:GmSSL 与系统 OpenSSL 的隔离(坑 #1、#2、#8)
  • 证书层:SM2 证书链的生成和验证(坑 #3、#4、#9)
  • 兼容层:客户端和中间件的国密支持(坑 #5、#6、#7、#11、#12)
最核心的建议是:在生产环境部署前,先在隔离环境中完整走通所有验证步骤。 国密 HTTPS 的调试难度远高于普通 HTTPS,因为工具链(GmSSL)的生态成熟度不如 OpenSSL,很多错误信息不够直观。

双证书方案是当前最务实的过渡策略,但它增加了配置复杂度。在国密浏览器普及率足够高之前,维护两套证书链是必要的成本。

参考来源

  • 国家密码管理局. GM/T 0024-2014《SSL VPN 技术规范》. 2014.
  • 国家密码管理局. GM/T 0015-2012《基于 SM2 算法的数字证书格式规范》. 2012.
  • 国家密码管理局. GM/T 0037-2014《证书认证系统检测规范》. 2014.
  • GmSSL 官方文档. https://gmssl.github.io/GmSSL/
  • Nginx 官方文档. https://nginx.org/en/docs/http/configuring_https_servers.html