4、国密SSL/TLS协议改造:从国际算法套件切换到国密套件,Nginx/OpenSSL国密适配,双证书部署策略

国密改造里,SSL/TLS协议这块是最容易踩坑的。说实话,很多团队把精力都放在应用层代码替换上,结果到了网络通信层才发现——浏览器连不上、证书链报错、握手失败。我最早做国密网关改造时,光调试TLS握手就花了两天。

今天咱们就把这块掰开揉碎讲清楚。核心就三件事:协议套件怎么换、Nginx怎么配、双证书怎么玩。

4.1 国际套件 vs 国密套件:到底差在哪?

先看一张对比表,心里有个底:

对比项 国际TLS 1.2/1.3 国密TLCP(GM/T 0024)
密钥交换 ECDHE、DHE ECDHE(SM2曲线)
签名算法 RSA、ECDSA SM2(基于椭圆曲线)
对称加密 AES、ChaCha20 SM4(128位分组)
摘要算法 SHA-256、SHA-384 SM3(256位)
证书格式 X.509 v3(RSA/ECC) X.509 v3(SM2公钥)
协议版本 TLS 1.2 / 1.3 TLCP v1.0 / v1.1

你可能会问:国密协议是不是完全另起炉灶?其实不是。TLCP(Transport Layer Cryptography Protocol)在设计上大量参考了TLS 1.2的框架,但把核心密码组件全换成了国密算法。说白了,握手流程还是Client Hello、Server Hello、证书交换、密钥协商那一套,但里面跑的算法变了。

关键差异点:

  • 国密必须使用SM2椭圆曲线(曲线参数固定,不是随便选一条)
  • SM4是唯一指定的对称加密算法,不支持AES回退
  • 证书链中所有证书必须使用SM2签名,不能混用RSA

4.2 双证书部署策略:为什么需要两本证?

这是国密改造里最让人困惑的地方。我刚开始也纳闷:一个域名为什么要配两本证书?

原因很简单——兼容性。国密浏览器(如360安全浏览器、红莲花)支持国密套件,但Chrome、Edge这些主流浏览器不支持。你不可能让用户先换浏览器再访问你的网站。

所以实际部署时,服务器同时持有两本证书:

  • 国密证书(SM2):用于国密浏览器,走TLCP协议
  • 国际证书(RSA/ECC):用于普通浏览器,走标准TLS协议

握手时怎么选?靠协议协商。客户端在Client Hello里带上自己支持的密码套件列表,服务端根据优先级选择。国密浏览器会优先发起国密套件,普通浏览器则发起国际套件。Nginx根据这个来决定用哪本证书。

我的经验:双证书部署时,国密证书和国际证书的CN/SAN必须完全一致。否则浏览器会报域名不匹配。我曾经因为国密证书少配了一个SAN,排查了整整一个下午。

4.3 Nginx + OpenSSL 国密适配实战

Nginx本身不支持国密,需要打补丁。目前主流方案有两种:

  1. 使用Tongsuo(原 BabaSSL):阿里开源的国密增强版OpenSSL,直接替换系统OpenSSL
  2. 使用GmSSL:北京大学开发的国密开源库,需要重新编译Nginx

我个人更推荐Tongsuo,因为它对Nginx的兼容性更好,而且社区活跃。下面以Tongsuo为例,讲一下具体步骤。

4.3.1 编译安装Tongsuo

# 下载Tongsuo源码
wget https://github.com/Tongsuo-Project/Tongsuo/archive/refs/tags/8.4.0.tar.gz
tar -xzf 8.4.0.tar.gz
cd Tongsuo-8.4.0

# 编译安装(启用国密支持)
./config --prefix=/usr/local/tongsuo \
         enable-ntls \
         enable-sm2 \
         enable-sm3 \
         enable-sm4
make -j$(nproc)
make install

注意:编译时一定要加 enable-ntls 参数,这是国密TLCP协议的支持开关。不加的话,编译出来的OpenSSL只能做国际TLS。

4.3.2 重新编译Nginx,链接Tongsuo

# 下载Nginx源码
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0

# 配置编译参数,指定Tongsuo路径
./configure --prefix=/usr/local/nginx \
            --with-openssl=/path/to/Tongsuo-8.4.0 \
            --with-openssl-opt="enable-ntls" \
            --with-http_ssl_module \
            --with-http_v2_module

make -j$(nproc)
make install

编译完成后,用 nginx -V 确认一下,看到 enable-ntls 就说明成功了。

4.3.3 Nginx双证书配置

这是最核心的部分。Nginx从1.11.0版本开始支持 ssl_certificate 指令的多证书配置,但国密场景下需要额外指定 ssl_certificate_sm2 和 ssl_certificate_key_sm2。

server {
    listen 443 ssl;
    server_name example.com;

    # 国际证书(RSA)
    ssl_certificate     /etc/nginx/certs/rsa.crt;
    ssl_certificate_key /etc/nginx/certs/rsa.key;

    # 国密证书(SM2)
    ssl_certificate_sm2     /etc/nginx/certs/sm2.crt;
    ssl_certificate_key_sm2 /etc/nginx/certs/sm2.key;

    # 国际密码套件
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:...';

    # 国密密码套件
    ssl_ciphers_sm2 'ECDHE-SM2-SM4-GCM-SM3:ECDHE-SM2-SM4-CBC-SM3';

    # 协议版本
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_protocols_sm2 TLCPv1.1;

    # 优先使用国密套件(如果客户端支持)
    ssl_prefer_server_ciphers on;
    ssl_prefer_server_ciphers_sm2 on;

    location / {
        proxy_pass http://backend;
    }
}

配置要点:

  • ssl_certificate_sm2 和 ssl_ciphers_sm2 是Tongsuo扩展的指令,标准Nginx没有
  • 国密套件名称以 SM 开头,如 ECDHE-SM2-SM4-GCM-SM3
  • 协议版本要分别指定:国际用 TLSv1.2 TLSv1.3,国密用 TLCPv1.1

4.4 国密握手流程:一张图看懂

下面这张图展示了国密TLCP协议的完整握手流程。我建议你保存下来,调试时对照着看。

国密TLCP握手流程 国密TLCP v1.1 握手流程 客户端(浏览器) 服务端(Nginx) 1. Client Hello 携带国密套件列表 2. Server Hello 选择国密套件 3. 服务端证书 SM2证书链 4. 验证证书 SM2签名验证 5. 密钥交换 ECDHE(SM2曲线) 6. 握手完成 SM4加密通信 说明:国密握手与TLS 1.2结构相似,但所有密码组件替换为国密算法(SM2/SM3/SM4)

4.5 避坑指南:我踩过的那些坑

这部分是我最想分享的。国密改造的坑,十个有八个出在证书和协议协商上。

坑1:证书链不完整

我曾经部署国密证书时,只上传了服务器证书,没上传中间CA证书。结果国密浏览器一直报"证书链不完整"。国密PKI体系里,中间CA证书必须和服务器证书一起下发,不能依赖浏览器内置。

解决方法:用 cat server.crt ca.crt > fullchain.crt 合并证书链。

坑2:SM2私钥格式不对

国密SM2私钥有自己特定的ASN.1结构,和RSA私钥完全不同。如果你用OpenSSL的 genrsa 生成私钥,那肯定不对。必须用 gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2。

验证方法:用 openssl ec -in sm2.key -text -noout 查看,确认曲线是 SM2。

坑3:Nginx配置顺序导致国密不生效

Nginx加载配置时,如果 ssl_certificate_sm2 写在 ssl_certificate 前面,某些版本会忽略国密配置。我建议严格按照:国际证书 → 国密证书 → 国际套件 → 国密套件的顺序写。

4.6 验证与测试

配置完成后,怎么确认国密生效了?我一般用三步验证法:

  1. 用国密浏览器访问:打开360安全浏览器,访问你的域名,地址栏出现绿色"国密"标识,说明国密握手成功
  2. 用curl模拟国密握手:
# 使用Tongsuo编译的curl
/path/to/tongsuo-curl -v --ciphers SM2-SM4-GCM-SM3 https://example.com

# 输出中看到 "Cipher is SM2-SM4-GCM-SM3" 即为成功
  1. 抓包确认:用Wireshark抓TLS握手包,查看Server Hello中的Cipher Suite字段,如果是 0xE0xx 开头的国密套件,就对了

小技巧:如果手头没有国密浏览器,可以用 openssl s_client -connect example.com:443 -ciphersuites "SM2-SM4-GCM-SM3" -groups "SM2" 来测试。注意要用Tongsuo版本的openssl。

4.7 性能与兼容性权衡

最后聊一个实际决策问题。双证书部署后,Nginx需要同时维护两套SSL上下文,内存占用会翻倍。我测过一个线上环境,单台Nginx的SSL内存从80MB涨到了150MB。

另外,国密SM2的签名验签性能比RSA 2048慢大约30%,比ECDSA P-256慢约50%。如果你的业务对TLS握手性能敏感,建议:

  • 开启Nginx的 ssl_session_cache,减少重复握手
  • 使用硬件密码卡(如国密HSM)加速SM2运算
  • 对非国密用户,优先走国际TLS,避免不必要的国密协商开销

嗯,国密SSL这块内容确实不少。但核心思路就一条:协议框架不变,换算法、换证书、换配置。只要把这三个环节打通,剩下的就是调优和运维了。