国密双证书方案实战:Nginx + SM2/RSA 双证书部署完整指南
什么是双证书方案
在国密改造过渡期,服务端同时部署国密算法证书(SM2)和国际算法证书(RSA/ECDSA),根据客户端能力自动协商使用哪种证书。
为什么需要双证书:
- 国密浏览器(360、奇安信等)支持 SM2 证书
- 普通浏览器(Chrome、Firefox)不支持 SM2 证书
- 双证书方案确保所有客户端都能正常访问
双证书 TLS 握手流程
CODE
ClientHello (支持国密套件 + 国际套件)
│
▼
服务端判断客户端能力
│
┌────┴────┐
│ │
▼ ▼
国密套件 国际套件
│ │
▼ ▼
SM2证书 RSA/ECDSA证书
│ │
▼ ▼
SM2+SM4 RSA/AES证书链生成
生成 SM2 证书链
BASH
#!/bin/bash
GMSSL_BIN="/usr/local/gmssl/bin/gmssl"
CA_DIR="./ca"
# 1. 生成 SM2 根 CA 密钥
$GMSSL_BIN ecparam -genkey -name SM2 -out $CA_DIR/sm2_root.key
# 2. 生成 SM2 根 CA 证书
$GMSSL_BIN req -new -x509 -key $CA_DIR/sm2_root.key \
-out $CA_DIR/sm2_root.crt \
-days 3650 \
-subj "/C=CN/O=TestOrg/CN=SM2 Root CA"
# 3. 生成 SM2 终端实体密钥
$GMSSL_BIN ecparam -genkey -name SM2 -out $CA_DIR/sm2_server.key
# 4. 生成 CSR
$GMSSL_BIN req -new -key $CA_DIR/sm2_server.key \
-out $CA_DIR/sm2_server.csr \
-subj "/C=CN/O=TestOrg/CN=localhost"
# 5. 签发证书
$GMSSL_BIN x509 -req -in $CA_DIR/sm2_server.csr \
-CA $CA_DIR/sm2_root.crt \
-CAkey $CA_DIR/sm2_root.key \
-CAcreateserial \
-out $CA_DIR/sm2_server.crt \
-days 365 \
-extfile <(printf "subjectAltName=DNS:localhost,DNS:*.example.com,IP:127.0.0.1")
echo "SM2 证书链生成完成"生成 RSA 证书链
BASH
#!/bin/bash
# generate_rsa_certs.sh - 生成国际证书链
OPENSSL_BIN="/usr/bin/openssl"
CA_DIR="./ca"
# 1. 生成 RSA 根 CA 密钥
$OPENSSL_BIN genrsa -out $CA_DIR/rsa_root.key 4096
# 2. 生成 RSA 根 CA 证书
$OPENSSL_BIN req -new -x509 -key $CA_DIR/rsa_root.key \
-out $CA_DIR/rsa_root.crt \
-days 3650 \
-subj "/C=CN/O=TestOrg/CN=RSA Root CA"
# 3. 生成 RSA 终端实体密钥
$OPENSSL_BIN genrsa -out $CA_DIR/rsa_server.key 2048
# 4. 生成 CSR
$OPENSSL_BIN req -new -key $CA_DIR/rsa_server.key \
-out $CA_DIR/rsa_server.csr \
-subj "/C=CN/O=TestOrg/CN=localhost"
# 5. 签发证书
$OPENSSL_BIN x509 -req -in $CA_DIR/rsa_server.csr \
-CA $CA_DIR/rsa_root.crt \
-CAkey $CA_DIR/rsa_root.key \
-CAcreateserial \
-out $CA_DIR/rsa_server.crt \
-days 365 \
-extfile <(printf "subjectAltName=DNS:localhost,DNS:*.example.com,IP:127.0.0.1")
echo "RSA 证书链生成完成"Nginx 双证书配置
基础双证书配置
NGINX
server {
listen 443 ssl;
server_name example.com;
# SM2 证书(国密)
ssl_certificate /etc/nginx/certs/sm2_server.crt;
ssl_certificate_key /etc/nginx/certs/sm2_server.key;
# RSA 证书(国际)
ssl_certificate /etc/nginx/certs/rsa_server.crt;
ssl_certificate_key /etc/nginx/certs/rsa_server.key;
# 密码套件配置
ssl_ciphers "ECDHE-SM2-WITH-SM4-SM3:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256";
# 协议版本
ssl_protocols TLSv1.2 TLSv1.3;
# 优先使用服务端密码套件顺序
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://backend;
}
}使用 GmSSL 的优化配置
如果使用 GmSSL 替换 OpenSSL,配置更简洁:
NGINX
server {
listen 443 ssl;
server_name example.com;
# 双证书配置(GmSSL 支持同一指令配置多个证书)
ssl_certificate /etc/nginx/certs/dual_cert.pem; # SM2 + RSA 合并
ssl_certificate_key /etc/nginx/certs/dual_key.pem;
# GmSSL 密码套件
SSL_ciphers "ECDHE-SM2-WITH-SM4-SM3:ECDHE-RSA-WITH-AES-256-GCM-SHA384";
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://backend;
}
}合并双证书文件
BASH
# 合并 SM2 和 RSA 证书到一个文件
cat sm2_server.crt rsa_server.crt > dual_cert.pem
cat sm2_server.key rsa_server.key > dual_key.pem客户端兼容性处理
国密浏览器检测
NGINX
# 通过 User-Agent 判断是否为国密浏览器
map $http_user_agent $is_gm_browser {
default 0;
"~*360SE" 1;
"~*360EE" 1;
"~*Qianxin" 1;
"~*GMSSL" 1;
}
server {
listen 443 ssl;
server_name example.com;
# 根据客户端类型选择证书
ssl_certificate /etc/nginx/certs/sm2_server.crt;
ssl_certificate_key /etc/nginx/certs/sm2_server.key;
# 国际客户端回退
ssl_certificate /etc/nginx/certs/rsa_server.crt;
ssl_certificate_key /etc/nginx/certs/rsa_server.key;
location / {
proxy_pass http://backend;
}
}双栈监听方案
更可靠的方案是使用两个监听端口:
NGINX
# 国密 HTTPS
server {
listen 8443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/certs/sm2_server.crt;
ssl_certificate_key /etc/nginx/certs/sm2_server.key;
ssl_ciphers ECDHE-SM2-WITH-SM4-SM3;
location / {
proxy_pass http://backend;
}
}
# 国际 HTTPS
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/certs/rsa_server.crt;
ssl_certificate_key /etc/nginx/certs/rsa_server.key;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
location / {
proxy_pass http://backend;
}
}踩坑记录
坑 1:Nginx 版本要求
Nginx 需要 1.11.5+ 才支持双证书配置。检查版本:
BASH
nginx -v如果版本过低,需要升级或重新编译。
坑 2:证书链完整性
SM2 证书链必须包含中间 CA 和根 CA,否则国密浏览器会报证书不受信任:
BASH
# 正确:包含完整证书链
cat sm2_server.crt sm2_intermediate.crt sm2_root.crt > sm2_fullchain.crt
# 错误:只有终端实体证书
cp sm2_server.crt sm2_fullchain.crt坑 3:密码套件协商
国密浏览器可能不支持某些密码套件组合,建议配置多个备选:
NGINX
ssl_ciphers "ECDHE-SM2-WITH-SM4-SM3:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";坑 4:OCSP Stapling
国密证书的 OCSP Stapling 需要额外配置:
NGINX
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/sm2_root.crt;
resolver 8.8.8.8 valid=300s;坑 5:性能影响
双证书会增加 TLS 握手开销,建议:
- 启用会话复用:
ssl_session_cache shared:SSL:10m; - 启用 OCSP Stapling 减少客户端验证开销
- 考虑使用 TLS 1.3 减少握手往返
验证部署
使用 GmSSL 验证国密连接
BASH
# 测试国密连接
gmssl s_client -connect localhost:443 -cipher ECDHE-SM2-WITH-SM4-SM3
# 查看证书详情
gmssl x509 -in sm2_server.crt -text -noout使用 OpenSSL 验证国际连接
BASH
# 测试国际连接
openssl s_client -connect localhost:443 -cipher ECDHE-RSA-AES256-GCM-SHA384
# 查看证书详情
openssl x509 -in rsa_server.crt -text -noout浏览器验证
- 国密浏览器访问:应显示 SM2 证书
- Chrome/Firefox 访问:应显示 RSA 证书
- 使用 Wireshark 抓包验证密码套件
总结
双证书方案是国密改造过渡期的务实选择。核心要点:
- 正确生成 SM2 和 RSA 证书链
- Nginx 配置支持双证书自动协商
- 注意证书链完整性和密码套件兼容性
- 做好性能优化(会话复用、OCSP Stapling)
- 充分测试各种客户端的兼容性
*参考来源:*
- *GM/T 0015-2012《基于 SM2 算法的数字证书格式规范》*
- *GM/T 0024-2014《SSL VPN 技术规范》*
- *Nginx 官方文档:ngx_http_ssl_module*
- *GmSSL 项目:https://github.com/guanzhi/GmSSL*