国密改造实战:TLCP双证书机制与Nginx配置全解析
2026/9/19 17:35:09 网站建设 项目流程

1. 为什么国密改造绕不开双证书这套机制

第一次接触国密改造的人,十有八九会卡在同一个问题上:为什么不能像普通 HTTPS 那样,一张证书打天下?我当初也是这么想的,直到把 TLCP 协议翻了个底朝天,才明白双证书不是设计者故意折腾人,而是国密体系里签名和加密两套算法在数学结构上就不一样,硬塞进一张证书里会出大问题。

先把背景说清楚。国密 TLS,也就是TLCP(Transport Layer Cryptography Protocol),它和咱们熟悉的 TLS 1.2/1.3 最大的区别,就是握手过程中需要两张证书:一张签名证书,一张加密证书。签名证书用的是 SM2 签名算法,负责身份认证、证明"我是我";加密证书用的是 SM2 加密算法,负责协商出会话密钥、保护后续通信数据。这两张证书可以来自同一个 CA,也可以是不同 CA 签发,但必须成对使用。

那为什么非要拆成两张?核心原因在于密钥用途的隔离。签名私钥一旦泄露,攻击者可以伪造你的身份去签任何东西,后果是身份被冒用;加密私钥泄露,攻击者能解密历史通信内容,后果是数据机密性丧失。把两种用途的密钥分开,等于把风险也隔离开——一把钥匙丢了不至于全盘皆输。这是密码学里"密钥分离"原则的典型落地,不是什么国密特有的怪癖,只是国密把它写进了协议强制要求。

再往深一层说,SM2 算法本身是基于椭圆曲线的,它的签名和加密虽然共用同一套曲线参数,但运算流程完全不同。签名走的是"私钥签名、公钥验签",加密走的是"公钥加密、私钥解密",如果强行用一对密钥同时干两件事,在密钥管理和安全证明上都会变得很别扭。TLCP 干脆规定:签名归签名,加密归加密,各用各的证书和密钥对。

理解了这一层,你就能明白为什么后面生成证书、配置 Nginx 的时候,处处都是"成对"出现——签名证书配签名私钥,加密证书配加密私钥,少一个都跑不起来。我见过不少人图省事,想拿一张证书糊弄过去,结果 Nginx 启动直接报错,或者握手阶段就断了,排查半天才发现是证书数量不对。

这篇文章面向的是正在做国密改造的运维和开发同学,尤其是需要在 Nginx 上落地 TLCP 双证书的场景。我会从 GmSSL 工具链的安装讲起,一路走到 Nginx 配置跑通、浏览器/客户端验证,把中间那些文档里不写、但实际一定会踩的坑都摊开讲。你不需要有密码学背景,但最好对 Nginx 和 HTTPS 的基本概念有点了解,这样读起来会更顺。

2. GmSSL 工具链的选型与安装踩坑

2.1 为什么是 GmSSL 而不是 OpenSSL

做国密,第一个绕不开的工具就是 GmSSL。很多人会问:OpenSSL 不是也能加国密引擎吗,为什么还要单独用 GmSSL?我实际对比过两条路线,结论是:如果你只是想让 OpenSSL 支持 SM2/SM3/SM4 算法,加引擎确实能凑合;但你要做 TLCP 双证书的完整握手,OpenSSL 的国密支持是残缺的,尤其是双证书的握手流程,原生 OpenSSL 根本不认。

GmSSL 是北大密码学团队主导的开源项目,它从底层就把 SM2、SM3、SM4、SM9 以及 TLCP 协议栈实现了一遍,命令行工具直接支持双证书的生成、签发、验证。换句话说,OpenSSL 是"给洋枪加个国密配件",GmSSL 是"从头造了一把国密枪"。做 TLCP 实战,用 GmSSL 是省心的选择。

不过这里有个版本坑要提前说。GmSSL 有 1.x、2.x、3.x 几个大版本,API 和命令行参数差异不小。做 TLCP 双证书,建议用 GmSSL 2.x 或 3.x,1.x 虽然也能生成 SM2 密钥,但双证书相关的命令支持不完整。我一开始图省事用了系统自带的 1.1 版本,结果发现gmssl sm2keygen这类命令根本没有,白白浪费了半天。

2.2 源码编译安装的完整步骤

GmSSL 在大多数 Linux 发行版的官方源里都没有,或者版本很老,所以基本都得源码编译。下面是我在 CentOS 7 和 Ubuntu 20.04 上都验证过的流程,照着走基本不会出问题。

先装编译依赖。CentOS 系:

yum install -y git gcc make cmake openssl-devel

Ubuntu/Debian 系:

apt-get install -y git gcc make cmake libssl-dev

然后拉源码、切分支、编译。这里我建议直接拉 3.x 的稳定 tag,别用 master,master 上偶尔会有半成品提交:

git clone https://github.com/guanzhi/GmSSL.git cd GmSSL git checkout v3.1.1 mkdir build && cd build cmake .. make -j$(nproc) make install

编译完之后,默认会装到/usr/local/bin/gmssl/usr/local/lib。这时候你直接敲gmssl version,如果报"找不到共享库",说明动态库路径没配。解决办法是加个配置:

echo "/usr/local/lib" >> /etc/ld.so.conf.d/gmssl.conf ldconfig

再敲gmssl version,能打印出版本号就说明装好了。这一步看着简单,但我见过太多人卡在"命令找不到"或者"库加载失败"上,其实都是路径问题。

提示:如果你是在内网或者离线环境部署,编译前先把源码包和依赖包都下好传进去。GmSSL 编译本身不依赖网络,但 cmake 阶段如果缺依赖会直接失败,提前把 gcc、make、cmake、openssl-devel 这几个装齐。

2.3 验证安装是否真的可用

装完之后别急着往下走,先做个小验证,确认 SM2 算法真的能跑。执行:

gmssl sm2keygen -pass 1234 -out sm2.key -pubout sm2.pub

这条命令会生成一对 SM2 密钥,私钥用密码 1234 加密存储。如果顺利生成sm2.keysm2.pub两个文件,说明工具链没问题。如果报错,大概率是版本不对或者库没加载,回头检查上一步。

我个人的习惯是,每装完一个密码工具,都先用它生成一对密钥、签一个自签名证书,跑通最小闭环再往下做。这样一旦后面出问题,你能快速判断是工具本身的问题,还是配置的问题,排查范围小很多。

3. 双证书的生成与签发:签名证书和加密证书怎么配成一对

3.1 先理清双证书的目录结构和命名

在动手生成之前,先把文件规划好,不然后面 Nginx 配置的时候会乱。我一般会建一个专门的目录,比如/etc/nginx/gmssl/,里面放这么几类文件:

  • CA 相关的:CA 私钥、CA 证书
  • 服务器签名证书:sign.crt+sign.key
  • 服务器加密证书:enc.crt+enc.key
  • 可选的证书链文件

命名上我强烈建议用signenc前缀区分,别用server1server2这种,时间一长你自己都忘了哪张是签名哪张是加密。这个习惯在排查问题时能救命——TLCP 握手失败时,你第一件事就是确认签名证书和加密证书有没有配反。

3.2 生成 CA 根证书

双证书体系里,签名证书和加密证书通常由同一个 CA 签发,所以先得有个 CA。用 GmSSL 生成 CA 的流程和 OpenSSL 类似,但命令参数是国密风格的。

先生成 CA 的 SM2 密钥:

gmssl sm2keygen -pass 123456 -out ca.key -pubout ca.pub

然后生成 CA 的自签名证书。这里要注意,CA 证书的 subject 里要标明它是 CA,扩展字段里 basicConstraints 要设成 CA:TRUE:

gmssl certgen -C CN -ST Beijing -L Haidian -O MyOrg -OU CA -CN "MyGMCA" \ -days 3650 \ -key ca.key -pass 123456 \ -key_usage keyCertSign,cRLSign \ -ca \ -out ca.crt

这条命令里几个参数值得说一下。-key_usage keyCertSign,cRLSign是给 CA 证书限定用途,只能用来签证书和签 CRL,不能拿去干别的;-ca表示这是一张 CA 证书,会带上 basicConstraints 扩展。这两个扩展如果漏了,后面用它签出来的服务器证书在严格校验的客户端上会被拒绝。

3.3 生成服务器签名证书

签名证书用于身份认证,keyUsage 要包含 digitalSignature 和 nonRepudiation。先生成签名私钥:

gmssl sm2keygen -pass 123456 -out sign.key -pubout sign.pub

然后生成 CSR(证书签名请求):

gmssl reqgen -C CN -ST Beijing -L Haidian -O MyOrg -OU Server -CN "gm.example.com" \ -key sign.key -pass 123456 \ -out sign.csr

最后用 CA 签发:

gmssl certsign -CA ca.crt -CAkey ca.key -CApass 123456 \ -days 365 \ -key_usage digitalSignature,nonRepudiation \ -ext_ku_critical \ -in sign.csr \ -out sign.crt

注意-ext_ku_critical这个参数,它把 keyUsage 扩展标记为 critical,意思是"这个用途限制是强制的,客户端必须遵守"。对于签名证书,这个标记很重要,能防止证书被挪作他用。

3.4 生成服务器加密证书

加密证书的流程几乎一样,但 keyUsage 要换成 keyEncipherment 和 dataEncipherment:

gmssl sm2keygen -pass 123456 -out enc.key -pubout enc.pub gmssl reqgen -C CN -ST Beijing -L Haidian -O MyOrg -OU Server -CN "gm.example.com" \ -key enc.key -pass 123456 \ -out enc.csr gmssl certsign -CA ca.crt -CAkey ca.key -CApass 123456 \ -days 365 \ -key_usage keyEncipherment,dataEncipherment \ -ext_ku_critical \ -in enc.csr \ -out enc.crt

到这里,你手上应该有ca.crtsign.crtsign.keyenc.crtenc.key这五个核心文件。签名证书和加密证书的 CN 可以相同,因为它们本来就是同一个服务器的两张证书,靠 keyUsage 区分用途。

3.5 验证证书链和用途是否正确

生成完别急着配 Nginx,先用 GmSSL 验证一下。查看证书内容:

gmssl certparse -in sign.crt

重点看输出里的 Key Usage 字段,签名证书应该是 digitalSignature 和 nonRepudiation,加密证书应该是 keyEncipherment 和 dataEncipherment。如果发现配反了,后面 Nginx 握手一定失败。

再验证证书链:

gmssl certverify -CA ca.crt -in sign.crt gmssl certverify -CA ca.crt -in enc.crt

两条都返回验证通过,说明证书签发没问题。这一步花不了两分钟,但能帮你挡掉后面一大堆莫名其妙的握手错误。

注意:签名证书和加密证书的私钥密码,在 Nginx 配置里需要以明文形式提供(或者用 Nginx 支持的密码文件机制)。生产环境里,私钥文件的权限一定要收紧到 600,属主设成 Nginx 运行用户,别让其他用户能读。

4. Nginx 支持 TLCP 的编译与配置落地

4.1 原生 Nginx 为什么不支持 TLCP

这是很多人踩的第一个大坑:兴冲冲装好 Nginx,把双证书往配置里一写,结果 Nginx 直接报"unknown directive ssl_certificate_sign"之类的错误。原因很简单——官方 Nginx 用的是 OpenSSL,而 OpenSSL 原生不支持 TLCP 双证书握手。你要么给 Nginx 打国密补丁,要么用已经集成好国密支持的 Nginx 分支。

目前主流做法有两种:一是用支持国密的 Nginx 分支(比如一些国内厂商维护的版本),二是自己给 Nginx 源码打 TLCP 补丁再编译。我两种都试过,自己打补丁更可控,但门槛高;用现成分支省事,但要注意版本和 GmSSL 的兼容性。

4.2 编译带国密支持的 Nginx

假设你已经装好了 GmSSL(前面第 2 节),现在编译 Nginx。先下源码:

wget https://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0

然后配置编译参数,关键是让 Nginx 链接到 GmSSL 而不是系统 OpenSSL:

./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-openssl=/path/to/GmSSL \ --with-openssl-opt="enable-tlcp" \ --with-cc-opt="-I/usr/local/include" \ --with-ld-opt="-L/usr/local/lib -Wl,-rpath,/usr/local/lib"

这里--with-openssl指向 GmSSL 源码目录,--with-openssl-opt打开 TLCP 支持。不同版本的 Nginx 和 GmSSL 参数名可能略有差异,如果 configure 报错,先看它提示哪个参数不认识,再对照 GmSSL 的文档调整。

编译安装:

make -j$(nproc) make install

装完之后nginx -V看一下,输出里应该能看到 GmSSL 相关的信息,确认链接的是国密库而不是系统 OpenSSL。

4.3 Nginx 双证书配置的完整写法

Nginx 的 TLCP 配置和普通 HTTPS 配置长得像,但多了几个关键指令。下面是我实际跑通的配置片段:

server { listen 443 ssl; server_name gm.example.com; # 开启 TLCP 协议 ssl_protocols TLCPv1.1; # 签名证书和私钥 ssl_certificate_sign /etc/nginx/gmssl/sign.crt; ssl_certificate_sign_key /etc/nginx/gmssl/sign.key; # 加密证书和私钥 ssl_certificate_enc /etc/nginx/gmssl/enc.crt; ssl_certificate_enc_key /etc/nginx/gmssl/enc.key; # 国密加密套件 ssl_ciphers ECC_SM4_CBC_SM3:ECC_SM4_GCM_SM3; ssl_prefer_server_ciphers on; # 会话缓存 ssl_session_cache shared:TLCP:10m; ssl_session_timeout 10m; location / { root /usr/share/nginx/html; index index.html; } }

几个关键点解释一下。ssl_protocols TLCPv1.1是告诉 Nginx 只走国密协议,别走国际 TLS;ssl_certificate_signssl_certificate_enc分别指定签名和加密证书,这是双证书配置的核心;ssl_ciphers里指定国密套件,ECC_SM4_GCM_SM3是带 GCM 认证加密的套件,安全性比 CBC 版本更高,优先用它。

4.4 配置校验与启动

配置写完,先别急着重启,用nginx -t校验语法:

/usr/local/nginx/sbin/nginx -t

如果报证书相关的错误,八成是路径不对或者证书格式有问题。校验通过后再 reload:

/usr/local/nginx/sbin/nginx -s reload

启动后看 Nginx 错误日志,确认没有握手相关的报错。日志一般在/usr/local/nginx/logs/error.log

提示:如果你在配置里同时保留了国际 TLS 的ssl_certificate指令,Nginx 可能会因为协议冲突启动失败。做纯国密改造时,把国际证书的配置注释掉,只留双证书配置。

5. 握手验证与常见故障的排查链路

5.1 用 GmSSL 命令行做客户端验证

Nginx 配好之后,怎么确认 TLCP 握手真的通了?最直接的办法是用 GmSSL 自带的客户端工具连一下:

gmssl s_client -connect gm.example.com:443 -tlcp -CAfile ca.crt

如果握手成功,你会看到协商出的协议版本是 TLCPv1.1,加密套件是 SM4 相关的,以及服务器返回的签名证书和加密证书。这一步能跑通,说明服务端配置基本没问题。

如果握手失败,GmSSL 会打印具体的错误信息,比如"certificate verify failed"或者"no shared cipher"。这些错误信息是排查的起点,别忽略。

5.2 握手失败的典型原因对照表

我把实际遇到过的握手失败原因整理成一张表,方便你对照排查:

报错信息可能原因排查方向
no shared cipher加密套件不匹配检查 ssl_ciphers 是否包含客户端支持的国密套件
certificate verify failed证书链不完整或 CA 不匹配用 certverify 验证证书链,确认客户端信任的 CA
unknown protocol协议版本不对确认 ssl_protocols 设成 TLCPv1.1
bad certificate证书用途配反检查签名证书和加密证书是否配反
handshake failure私钥密码错误或权限问题检查私钥文件权限和密码

这张表是我踩了无数次坑之后总结的,基本上照着查能覆盖八成以上的问题。

5.3 一个真实的排查案例

说个我印象最深的坑。有次配好双证书,nginx -t通过,reload 也成功,但客户端死活连不上,报"handshake failure"。我先查了证书用途,没问题;又查了加密套件,也对。折腾了快一个小时,最后发现是私钥文件的权限问题——Nginx 运行用户是nginx,但私钥文件属主是root,权限 600,Nginx 读不到私钥,握手自然失败。

这个坑的隐蔽之处在于,Nginx 启动和 reload 都不会报权限错误,只有真正握手的时候才会失败。解决办法很简单:

chown nginx:nginx /etc/nginx/gmssl/*.key chmod 600 /etc/nginx/gmssl/*.key

改完权限,握手立刻通了。从那以后,我每次配完双证书,第一件事就是检查私钥权限和属主,这个习惯帮我省了大量排查时间。

5.4 浏览器和客户端的兼容性说明

命令行验证通过,不代表浏览器就能用。国密 TLCP 在主流浏览器上的支持情况比较特殊,普通 Chrome、Firefox 默认是不支持 TLCP 的,需要专门的国密浏览器或者装了国密模块的浏览器。这一点在做方案设计的时候就要考虑清楚——如果你的用户群体用的是普通浏览器,那纯国密方案可能跑不通,得考虑国密和国际 TLS 双栈并存的方案。

双栈配置的思路是:同一个 443 端口,根据客户端支持的协议自动选择。这需要 Nginx 同时加载国际证书和国密双证书,配置上会复杂一些,但兼容性最好。具体做法是在 server 块里同时配置ssl_certificate(国际)和ssl_certificate_sign/ssl_certificate_enc(国密),让 Nginx 根据 ClientHello 自动协商。

6. 生产环境落地时我踩过的那些坑

6.1 证书有效期和轮换的坑

国密证书的有效期管理,比国际证书更容易被忽视。我见过一个项目,证书签了 365 天,结果没人记得续期,到期当天服务直接不可用。国密证书的轮换比国际证书麻烦,因为双证书要同时换,而且签名证书和加密证书的 keyUsage 不能搞混。

我的建议是:把证书到期时间写进监控,提前 30 天告警。轮换的时候,先生成新的双证书,验证通过后再替换,替换后 reload Nginx,最后用客户端验证一遍。整个过程最好做成脚本,减少手工操作出错的可能。

6.2 私钥密码管理的坑

GmSSL 生成的私钥默认是加密存储的,需要密码才能读取。Nginx 配置里怎么提供这个密码?不同版本的国密 Nginx 支持方式不一样,有的支持ssl_password_file指令,有的要求私钥必须是明文。

如果必须用明文私钥,那文件权限就是最后一道防线。我的做法是:私钥文件权限 600,属主是 Nginx 运行用户,放在只有 root 和 Nginx 用户能访问的目录里。同时,绝对不要把私钥提交到代码仓库,哪怕是私有仓库也不行。

6.3 性能调优的几个实际参数

国密算法的性能比国际算法略低,尤其是 SM2 的签名和验签。在高并发场景下,握手阶段的 CPU 开销会比较明显。我实测下来,几个调优参数比较有效:

  • ssl_session_cache shared:TLCP:10m:开启会话缓存,减少重复握手。10m 大概能缓存 4 万个会话,够大多数场景用。
  • ssl_session_timeout 10m:会话超时时间,太长占内存,太短起不到缓存效果,10 分钟是个平衡点。
  • worker_processes auto:让 Nginx 自动根据 CPU 核数设置 worker 数量。
  • ssl_buffer_size 4k:减小握手缓冲区,降低内存占用。

这些参数不是拍脑袋定的,是我在不同压力下反复测出来的。你可以根据自己的硬件和并发量微调,但大方向是:会话缓存一定要开,worker 数量要匹配 CPU。

6.4 日志和监控要盯什么

国密改造上线后,监控要重点关注几个指标:握手成功率、握手耗时、证书剩余有效期、Nginx 错误日志里的 SSL 相关报错。握手成功率突然下降,通常是证书或配置出了问题;握手耗时上升,可能是 CPU 瓶颈或者会话缓存失效。

我一般会在 Nginx 的 access log 里加上$ssl_protocol$ssl_cipher两个变量,这样能直接看到每个请求用的是不是 TLCP、用的哪个套件。排查问题时,这两个字段能帮你快速定位是协议协商问题还是应用层问题。

log_format gmssl '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$ssl_protocol" "$ssl_cipher"';

加上这个日志格式,出问题时看一眼日志,协议和套件一目了然,比翻配置文件快多了。

6.5 离线环境部署的注意事项

很多国密改造项目是在内网或离线环境做的,这时候 GmSSL 和 Nginx 的依赖包都得提前准备好。我的经验是:在一台能联网的机器上,把 GmSSL 源码、Nginx 源码、所有编译依赖(gcc、make、cmake、openssl-devel 等)的 RPM 或 deb 包全部下载下来,打包传到离线环境。

离线环境最容易缺的是 cmake 和 openssl-devel,这两个不装,GmSSL 编译直接失败。另外,编译好的 GmSSL 动态库要记得ldconfig,否则 Nginx 启动时会报找不到库。这些细节看着琐碎,但离线环境里出问题,排查起来比联网环境麻烦得多,提前准备充分能省大量时间。

7. 从双证书到完整国密改造的延伸思考

双证书跑通只是国密改造的第一步。真正完整的国密改造,还涉及应用层的 SM2 加解密、SM3 摘要、SM4 对称加密,以及和现有系统的兼容。我做过几个项目之后,最大的体会是:国密改造不是换个证书就完事,而是一次系统性的密码基础设施升级

比如,你的后端服务如果之前用的是 RSA 做数据签名,改造后要换成 SM2;数据库里的敏感字段如果之前用 AES 加密,改造后要换成 SM4。这些改动涉及代码、数据迁移、密钥管理,工作量远比配个 Nginx 大。所以做方案的时候,一定要把范围界定清楚,是只做传输层国密,还是全链路国密,两者的复杂度差一个数量级。

另外,国密算法的生态还在完善中,工具链、库、文档的成熟度不如国际算法。这意味着你会遇到更多"文档没写、社区没答案"的问题,需要自己啃源码、抓包分析。我个人的习惯是,遇到问题先抓包,看握手流程卡在哪一步,再对照 TLCP 协议规范找原因。这个方法虽然笨,但最可靠。

最后分享一个实用的小技巧:做国密改造时,先在测试环境把整条链路跑通,包括证书生成、Nginx 配置、客户端验证、应用层加解密,全部验证通过后再上生产。生产环境第一次上线,最好选在业务低峰期,并且准备好回滚方案——万一握手出问题,能快速切回原来的国际 TLS 配置,把影响降到最低。这套流程我在多个项目里用过,虽然保守,但稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询