国密 HTTPS 踩坑实录:从编译到上线的 12 个血泪教训
前言
国密 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 混用。
排查:
# 检查系统 OpenSSL 头文件位置
dpkg -L libssl-dev | grep evp.h
# 检查是否同时存在两个 OpenSSL
openssl version
/usr/local/ssl/bin/openssl version解决:
# 方案 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验证:
/usr/local/gmssl/bin/openssl version
# 应输出 GmSSL 版本信息,而非系统 OpenSSL坑 #2:Nginx 编译时找不到 GmSSL 的 SM2 实现
现象: Nginx 编译通过,但启动时报错 SSL_CTX_use_certificate: ee key too small 或 unsupported certificate。
原因: Nginx 的 ngx_http_ssl_module 默认使用 OpenSSL 的证书验证逻辑,对 SM2 证书的 OID 识别不完整。GmSSL 3.x 将国密算法实现为 Provider,但 Nginx 可能仍链接到系统 OpenSSL 的 libcrypto。
排查:
# 检查 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解决:
# 重新编译 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验证:
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。
排查:
# 检查证书链内容
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解决:
# 正确的证书链拼接顺序
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;验证:
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 根据客户端能力自动选择。
server {
listen 443 ssl;
server_name your-domain.com;
# 国密证书
ssl_certificate /path/to/sm2-fullchain.pem;
ssl_certificate_key /path/to/sm2-key.pem;
# RSA 证书(备用)
ssl_certificate /path/to/rsa-fullchain.pem;
ssl_certificate_key /path/to/rsa-key.pem;
# 密码套件优先级
ssl_ciphers "ECDHE-SM2-SM4-SM3:ECDHE-RSA-AES256-GCM-SHA384";
}注意: Nginx 1.24+ 支持多证书配置,但需要编译时启用验证:ssl_certificate多证书支持。旧版本需要使用ssl_certificate指令的变量形式。
# 国密浏览器(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 根证书。
排查:
import ssl
print(ssl.OPENSSL_VERSION)
# 如果是系统 OpenSSL,不支持 SM2解决:
# 方案 A:使用 GmSSL 的 Python 绑定(推荐)
# pip install gmssl
from gmssl import sm2, func
# 方案 B:在 requests 中指定 CA 包
import requests
response = requests.get(
'https://your-domain.com',
verify='/path/to/gm-ca-bundle.crt' # 包含 SM2 根 CA 的证书包
)
# 方案 C:使用 urllib3 的自定义 SSL 上下文
import ssl
import urllib3
ctx = ssl.create_default_context(cafile='/path/to/gm-ca-bundle.crt')
http = urllib3.PoolManager(ssl_context=ctx)
response = http.request('GET', 'https://your-domain.com')验证:
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 mismatch 或 certificate verify failed。
原因: MySQL 客户端(mysql-client 或各语言的 MySQL 驱动)通常基于系统 OpenSSL,不支持 SM2 证书的 TLS 连接。
排查:
# 检查 MySQL 客户端 SSL 库
ldd /usr/bin/mysql | grep ssl
# 测试 SSL 连接
mysql --ssl-mode=REQUIRED -h your-mysql-host -u user -p -e "STATUS;" | grep SSL解决:
# 方案 A:MySQL 服务端配置双证书(推荐)
# my.cnf
[mysqld]
ssl-ca=/path/to/ca.pem
ssl-cert=/path/to/server-cert.pem
ssl-key=/path/to/server-key.pem
# 同时支持 RSA 和 SM2 证书(MySQL 8.0.26+)
# 方案 B:客户端使用 RSA 证书连接
mysql --ssl-mode=REQUIRED --ssl-ca=/path/to/rsa-ca.pem -h host -u user -p
# 方案 C:Python 中使用 PyMySQL + 自定义 SSL
import pymysql
connection = pymysql.connect(
host='your-mysql-host',
user='user',
password='password',
ssl={'ca': '/path/to/rsa-ca.pem'}
)坑 #7:CDN 不支持国密 TLS
现象: 接入 CDN 后,国密 HTTPS 访问失败,或 CDN 回源时使用 RSA 证书。
原因: 大多数 CDN 厂商(阿里云、腾讯云、Cloudflare 等)的边缘节点不支持国密 TLS 协议。CDN 会在边缘终止 TLS,然后用普通 HTTPS 回源。
排查:
# 检查 CDN 返回的证书
curl -v https://your-domain.com 2>&1 | grep "Server certificate"
# 如果看到 RSA 证书,说明 CDN 做了 SSL 卸载解决:
# 方案 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。
排查:
which openssl
openssl version
# 如果输出 GmSSL,说明系统 OpenSSL 被覆盖解决:
# 方案 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 默认生成的证书可能缺少 digitalSignature 或 keyAgreement 标记。
排查:
/usr/local/gmssl/bin/openssl x509 -in server-cert.pem -text -noout | grep -A2 "Key Usage"解决:
# 创建配置文件 cat > sm2-cert.cnf << 'EOF'
[req]
distinguished_name = req_dn
req_extensions = v3_req
[req_dn]
CN = your-domain.com
[v3_req]
keyUsage = digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = your-domain.com
DNS.2 = *.your-domain.com
EOF
# 使用配置文件生成证书
/usr/local/gmssl/bin/openssl req -new -key sm2-key.pem -out sm2.csr -config sm2-cert.cnf
/usr/local/gmssl/bin/openssl x509 -req -in sm2.csr -CA intermediate-ca.pem -CAkey intermediate-ca-key.pem -out server-cert.pem -days 365 -extfile sm2-cert.cnf -extensions v3_req坑 #10:Nginx 国密 TLS 性能远低于预期
现象: 国密 HTTPS 的 QPS 只有 RSA 的 1/5,延迟高出 3 倍。
原因: SM2 签名验证的计算开销远高于 RSA-2048。GmSSL 的软件实现没有针对 x86 架构做充分的 SIMD 优化。
排查:
# 使用 openssl speed 测试
/usr/local/gmssl/bin/openssl speed sm2
openssl speed rsa2048
# 使用 wrk 压测
wrk -t4 -c100 -d30s https://your-domain.com解决:
# 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 protocol 或 no shared cipher。
原因: 客户端和服务端没有共同的国密密码套件。GmSSL 默认启用的套件与 Nginx 配置的不一致。
排查:
# 查看 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 配置国密密码套件
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 response 或 OCSP_basic_verify failed。
原因: 双证书方案中,Nginx 需要为两套证书分别配置 OCSP Stapling,但大多数配置只配了一套。
排查:
# 检查 OCSP 响应
/usr/local/gmssl/bin/openssl ocsp -issuer intermediate-ca.pem -cert server-cert.pem -url http://ocsp.your-ca.com -resp_text解决:
# Nginx 双证书 + OCSP Stapling 配置
server {
listen 443 ssl;
server_name your-domain.com;
# SM2 证书
ssl_certificate /path/to/sm2-fullchain.pem;
ssl_certificate_key /path/to/sm2-key.pem;
# RSA 证书
ssl_certificate /path/to/rsa-fullchain.pem;
ssl_certificate_key /path/to/rsa-key.pem;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/sm2-fullchain.pem;
# resolver
resolver 8.8.8.8 114.114.114.114 valid=300s;
resolver_timeout 5s;
}注意: 如果 CA 的 OCSP 响应器不支持国密签名,OCSP Stapling 可能失败。此时可以暂时关闭 ssl_stapling_verify(不推荐生产环境),或联系 CA 确认 OCSP 响应器的国密支持情况。
检查清单
完成国密 HTTPS 部署后,按以下清单逐项验证:
# 1. 证书链完整性
/usr/local/gmssl/bin/openssl verify -CAfile root-ca.pem -untrusted intermediate-ca.pem server-cert.pem
# 2. 证书 Key Usage
/usr/local/gmssl/bin/openssl x509 -in server-cert.pem -text -noout | grep -A3 "Key Usage"
# 3. TLS 连接测试
/usr/local/gmssl/bin/openssl s_client -connect your-domain.com:443 -cipher "ECDHE-SM2-SM4-SM3" < /dev/null
# 4. 密码套件协商
nmap --script ssl-enum-ciphers -p 443 your-domain.com
# 5. OCSP Stapling
/usr/local/gmssl/bin/openssl s_client -connect your-domain.com:443 -status < /dev/null | grep "OCSP Response Status"
# 6. HTTP 响应头
curl -I https://your-domain.com | grep -i "strict-transport-security"
# 7. 双证书验证(RSA 客户端)
curl -v https://your-domain.com 2>&1 | grep "Server certificate"总结
国密 HTTPS 部署的坑主要集中在三个层面:
- 编译层:GmSSL 与系统 OpenSSL 的隔离(坑 #1、#2、#8)
- 证书层:SM2 证书链的生成和验证(坑 #3、#4、#9)
- 兼容层:客户端和中间件的国密支持(坑 #5、#6、#7、#11、#12)
双证书方案是当前最务实的过渡策略,但它增加了配置复杂度。在国密浏览器普及率足够高之前,维护两套证书链是必要的成本。
参考来源
- 国家密码管理局. 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