做密评和等保合规这些年,我抓过最多的包,大概就是国密HTTPS的握手包了。很多人第一次接触国密HTTPS时,第一反应是:这不就是TLS把RSA换成SM2、把AES换成SM4吗?但真正上手抓包才发现,从证书体系到握手流程,再到Wireshark这类工具的识别和解析,每一步都和普通HTTPS不太一样。这篇文章就把我在国密HTTPS抓包这件事上踩过的坑、总结出的流程完整整理一遍,重点讲清楚国密握手协议到底怎么走,以及如何用Wireshark把一次国密握手从头到尾抓明白、看懂它。
这篇内容适合正在做国密改造、密评整改、开发国密网关的运维和研发同学,也适合刚接触商用密码、对TLS协议栈有兴趣的安全测试人员。不需要你有多深的密码学基础,只要熟悉一点点Wireshark的基本操作,跟着文章走一遍,就能在自己的环境里复现一次完整的国密HTTPS握手抓包。
1. 国密HTTPS是什么,它和普通HTTPS差在哪
1.1 为什么会有国密HTTPS
国密HTTPS,通俗讲就是“用国产密码算法完成TLS/SSL握手和通信加密的HTTPS”。它遵循的标准主要是GMT 0024-2014《SM2密码算法加密签名消息语法规范》和相关的SSL VPN技术规范,底层用到的核心算法是SM2、SM3、SM4。
很多人问,为什么不能直接继续用RSA和AES?从工程角度看,并不是说国际算法不安全,而是政企、金融、能源这类系统在做等保测评、密评时,监管要求里明确提到了对国密算法的应用和改造。银行网银、政务App、内部办公系统现在都在陆续做国密整改,而这套整改落地的网络层入口,就是先把HTTPS的密码套件切到国密上。
我实际接触下来的感觉是,国密HTTPS的难点不是在算法本身,而是在兼容性和调试手段上。普通HTTPS抓包资料一搜一大把,Fiddler、Charles、Wireshark的教程满地都是,但一换成国密套件,很多通用工具直接“哑火”,要么不认识套件,要么在握手阶段就断了,这也是我写这篇文章最想帮大家解决的问题。
1.2 双证书体系:签名证书和加密证书
国密HTTPS最明显的一个设计差异,是采用“双证书”体系。标准TLS一般只有一个证书,用来承载公钥、完成身份认证和密钥协商;但在国密SSL协议里,服务端必须同时准备两张证书:
- 签名证书:用于对握手过程中的关键参数做签名,相当于服务端的“身份证明”。证书本身的签发算法是SM2-with-SM3,证书里的公钥是SM2签名公钥。
- 加密证书:用于密钥交换。客户端拿到加密证书里的SM2加密公钥后,把预主密钥用SM2加密算法保护起来传给服务端。
这个设计不是拍脑袋想出来的,主要原因在于SM2算法是“一钥一用”的。RSA的密钥既可以用来签名,也可以用来加密,但SM2如果同一对密钥既做签名又做加密,会带来安全风险。所以国密协议直接把两类用途拆开,用两张证书各管一摊。
在Wireshark里抓包时,你会看到ServerCertificate这条消息里出现了两条证书链,或者一条证书链里包含两张证书,这就和普通HTTPS的单证书返回有明显区别。第一次抓包时别以为是服务端配置错误,这是国密的正常形态。
1.3 抓包难度为什么比普通HTTPS高
普通HTTPS抓包要解密,最常用的思路是中间人(MITM),让客户端信任一个本地根证书,再由抓包工具动态签发目标站点证书完成代理。但这套逻辑在国密HTTPS下基本行不通,原因有三:
- 国密站点部署在专用浏览器或专用客户端环境下,证书信任链通常被严格控制,外部安装的根证书不一定被信任;
- 国密套件里的证书是SM2签名证书,通用代理工具临时生成的证书基本都是RSA或ECDSA,无法和国密套件匹配;
- 很多国密网关会做双向认证,客户端也需要持证访问,代理模式很难同时模拟客户端和服务端的身份。
所以正确的思路,是在不介入通信路径的前提下,用Wireshark旁路抓包,再结合本地私钥或密钥导出能力做解密验证。这篇文章后面的内容,就是围绕这个思路展开的。
2. 国密HTTPS握手协议流程逐段拆解
国密HTTPS的握手流程整体框架还是经典TLS的“四次握手”,但每一步都有国密特色。我习惯把它分成四个阶段来理解,抓包的时候也按这个逻辑去对照报文。
2.1 第零步:套件协商,客户端先亮出国密家底
所有TLS握手都是从ClientHello开始的。国密客户端的ClientHello里,会在Cipher Suites字段带上一串国密套件,常见的有:
- ECC_SM4_CBC_SM3
- ECC_SM4_GCM_SM3
- ECDHE_SM4_CBC_SM3(部分较新的实现支持)
- ECDHE_SM4_GCM_SM3
对照普通HTTPS里的TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256这类套件名,你会发现国密套件的关键词全是SM4、SM3,没有RSA也没有AES。套件命名的含义是:密钥交换用SM2(ECC在这里指SM2椭圆曲线)、对称加密用SM4、消息认证和PRF使用SM3。
Wireshark里抓到的ClientHello,如果展开Cipher Suites,能看到这些套件的名字。部分老版本Wireshark可能识别成Unknown,需要升级到较新版本,或者手工查套件ID。这时候可以对照GMT 0024里的套件分配编号来看,新版Wireshark基本都能直接显示名称。
2.2 第一阶段:ServerHello和证书下发
服务端收到ClientHello后,从客户端支持的套件列表里挑一个国密套件,通过ServerHello返回给客户端。紧接着就是ServerCertificate,把签名证书和加密证书一起发给客户端。
这里有一个非常值得抓包观察的点:加密证书不是独立下发的,它要由签名证书的私钥进行签名绑定。也就是说,签名证书除了证明服务端身份,还负责“担保”加密证书的有效性。这个设计防止了加密公钥在传输过程中被替换,相当于给密钥交换加了一道身份锁。
在Wireshark的ServerCertificate报文里,你可以展开Certificate字段,通常能看到一条证书列表,第一个是签名证书,第二个是加密证书。点开每张证书,算法标识都能看到SM2withSM3字样。
2.3 第二阶段:密钥交换,SM2加密预主密钥
普通HTTPS里,RSA密钥交换模式是客户端用RSA公钥加密预主密钥;国密方案也类似,但用的是SM2加密公钥。
客户端在这一步会做两件事:
- 本地生成一个48字节的预主密钥(PreMasterSecret);
- 用服务端加密证书里的SM2公钥,对这个预主密钥做SM2加密,然后把密文放到ClientKeyExchange消息里发出去。
SM2加密的密文格式和RSA不一样,它不是简单的一段填充后的模幂结果,而是由C1(椭圆曲线点)、C3(杂凑值)、C2(对称加密后的密文)三段拼接而成。抓包时观察ClientKeyExchange的长度,会比普通RSA模式明显不同。48字节的预主密钥,SM2加密后大概会变成144字节左右,具体长度取决于实现细节。
这一段在Wireshark里如果只是网络层解析,你会看到Encrypted PreMaster Secret字段,但看不到明文。要验证这一步是否成功,一般在服务端看握手是否继续,或者配合私钥做解密。
2.4 第三阶段:密钥派生与Finished校验
客户端发出ClientKeyExchange之后,双方其实已经握有足够的材料来派生会话密钥了。国密TLS的密钥派生逻辑和标准TLS类似,只是把PRF底层从SHA-256换成了SM3:
- 主密钥 MS = PRF(PreMasterSecret, "master secret", ClientHello.random + ServerHello.random)
- 工作密钥 KeyBlock = PRF(MS, "key expansion", ServerHello.random + ClientHello.random)
工作密钥按照协议顺序切成客户端写密钥、服务端写密钥、CBC模式还要加IV。SM4-CBC模式下会分配IV,SM4-GCM模式下则主要是密钥和nonce。
密钥派生完成后,双方会各自发Finished消息,内容是用握手过程中所有消息的摘要值运算出来的验证数据。Wireshark看到ChangeCipherSpec之后紧跟着的Finished消息,就说明握手已经走到收尾阶段了。如果抓包时停在ClientKeyExchange后没有下文,基本可以判断是密钥协商失败,这个在后面的排查章节会细说。
2.5 单向认证和双向认证的流程差异
国密HTTPS在政务和金融场景里经常开双向认证,也就是客户端也要持证上岗。多出来的步骤主要在两个地方:
- 服务端在ServerHelloDone之前会发CertificateRequest;
- 客户端在ClientKeyExchange之后、ChangeCipherSpec之前,会补发自己的签名证书和CertificateVerify,用客户端签名证书的私钥对握手消息摘要做签名。
抓包时如果发现ServerHello之后多了一条CertificateRequest,而且后面ClientKeyExchange附近出现了两条来自客户端的证书相关消息,就说明这是一次双向国密握手。排查双向认证问题时,重点看客户端证书链是否完整、客户端签名公钥是否被服务端信任。
3. 实操:本地搭建国密环境并完成抓包
理论说再多,不如真实抓一次包。这一章我会带你在本地搭一套最简国密环境,用GmSSL生成双证书、启动测试服务,然后用Wireshark把一次完整国密握手抓下来。整套操作在Ubuntu 22.04上验证过,Windows下装了GmSSL也基本通用。
3.1 准备GmSSL环境
GmSSL是国密生态里最常用的开源工具库,底层实现了SM2、SM3、SM4以及国密TLS协议。它有两种形态:一种是完整命令行工具,一种是OpenSSL风格的库。我们这里用命令行工具就够。
安装方式我建议直接编译源码,虽然是国密但编译过程很常规:
git clone https://github.com/guanzhi/GmSSL.git cd GmSSL mkdir build && cd build cmake .. make -j4 sudo make install sudo ldconfig编译完成后验证一下:
gmssl version如果能正常输出版本信息,环境就绪。如果系统里同时装了其他OpenSSL,注意命令名是gmssl,和openssl区分开。
这里说明一下为什么不直接用系统自带的openssl:标准openssl默认编译并不包含SM2签名、SM4-CBC套件这些国密TLS能力,尤其在握手协议层面,还需要特定的国密套件注册表。用GmSSL可以省掉自己往openssl里打补丁的工作量。
3.2 生成SM2签名证书和加密证书
用GmSSL可以很方便地生成SM2密钥对和自签名证书。我写了一个简单的脚本流程,照着执行就行。
mkdir ~/gmtest && cd ~/gmtest # 生成签名密钥对 gmssl ecparam -genkey -name sm2p256v1 -out sign_key.pem # 生成签名证书 gmssl req -new -key sign_key.pem -out sign_req.pem -subj "/CN=gmtest-sign" gmssl x509 -req -in sign_req.pem -signkey sign_key.pem -days 365 -out sign_cert.pem # 生成加密密钥对 gmssl ecparam -genkey -name sm2p256v1 -out enc_key.pem # 生成加密证书 gmssl req -new -key enc_key.pem -out enc_req.pem -subj "/CN=gmtest-enc" gmssl x509 -req -in enc_req.pem -signkey sign_key.pem -days 365 -out enc_cert.pem注意,加密证书虽然名义上是“加密用途”,但我这里是直接用签名证书的私钥给它签发的,这样在测试环境里能模拟出“签名证书担保加密证书”的绑定关系。更正规的做法是用独立的CA私钥去签这两张证书,然后把签名CA的根证书发给客户端信任。本地测试用自签名问题也不大。
生成完以后,目录下会有六个文件。sign_cert.pem和enc_cert.pem这两个证书,就是后面服务端要加载的。
3.3 启动国密服务端并抓包
用GmSSL自带的服务端工具拉起一个国密TLS测试服务:
gmssl s_server -accept 4433 -cert sign_cert.pem -key sign_key.pem -enc_cert enc_cert.pem -enc_key enc_key.pem -www参数解释一下:-cert和-key是签名证书及私钥,-enc_cert和-enc_key是加密证书及私钥,-www表示用HTTP响应来测试,浏览器访问能看到简单页面。GmSSL的s_server会默认优先协商国密套件。
服务端起来后,另开一个终端启动Wireshark,选择回环接口lo(本地测就是回环,真实环境抓服务器网卡)。点左上角绿色鲨鱼图标开始抓包,过滤条件先随便填tcp.port == 4433。
然后回终端,用GmSSL的客户端工具发起连接:
gmssl s_client -connect 127.0.0.1:4433 -brief这时候Wireshark里就会捕获到一组完整的TCP+TLS报文流。如果一切正常,客户端会输出握手成功的提示,服务端也会有访问日志。
3.4 Wireshark里的关键过滤表达式
抓包文件里数据很多,直接看会眼花。我常用的过滤表达式有这几个:
tcp.port == 4433 tls.handshake.type == 1 tls.handshake.type == 2 tls.handshake.type == 11 tls.handshake.type == 16 tls.handshake.type == 20含义分别对应:所有流量、ClientHello、ServerHello、Certificate、ClientKeyExchange、Finished。你可以在过滤栏里逐个试,也可以用tls.handshake.type in {1,2,11,16,20}一次全筛出来。
展开ClientHello里的Cipher Suites字段,正常情况下能看到GmSSL客户端带上了国密套件,名称形如ECC_SM4_CBC_SM3或ECC_SM4_GCM_SM3。看到这个,就说明你的抓包点选对了,后面所有报文都和国密握手相关。
4. 从抓包结果解读一次完整国密握手
抓包只是手段,看懂报文才是目的。这个章节我按实际抓到的一个会话,把每一步关键报文拆开讲。
4.1 ClientHello:看扩展和套件优先级
打开ClientHello,除了IP和TCP头,重点看两个地方。
第一个是Cipher Suites列表。GmSSL客户端默认会把好几个国密套件放进列表,越靠前的优先级越高。实际生产环境里,客户端浏览器(比如密信浏览器、红莲花安全浏览器)也有一套自己的套件优先级,抓包时可以看到客户端最倾向哪个套件。
第二个是Supported Groups和Signature Algorithms。国密TLS里这里会出现sm2p256v1这类曲线名,签名算法列表里能看到SM2withSM3。如果在扩展里找不到SM2相关内容,而套件列表里又有国密套件,多数情况是因为服务端不支持SM2椭圆曲线,客户端在后续步骤会降级或中止。
我自己的习惯是,抓包后第一眼不急着看套件,先看Supported Groups有没有sm2p256v1。这个字段如果丢了,后面SM2密钥交换基本走不通。
4.2 ServerHello和Certificate:识别套件确认与双证书
服务端返回的ServerHello里,Cipher Suite字段会明确写出本次协商使用的国密套件。后面紧跟的Certificate消息则包含两张证书。Wireshark的Certificate字段下会列出Transport Layer Security -> TLS -> Certificate -> Certificates,展开后能看到证书1和证书2。
证书1是签名证书,证书2是加密证书。点开证书详情,Signature Algorithm显示为sm2-with-sm3或者SM2withSM3。如果你用的是老版本Wireshark,这里可能显示成Unknown Algorithm,但只要能看到两个证书的公共名称(CN)分别是签名和加密相关的主机名,基本能判断证书关系正常。
在生产环境里,双证书经常来自同一个CA,或者由不同的CA分别签发。这种情况下,Certificate消息可能会带两条证书链。抓包时如果发现只有一张证书,而服务端配置了双证书,基本可以判断是服务端漏配了enc_cert参数,或者网关配置逻辑没走对。
4.3 ClientKeyExchange:SM2密文的长度特征
ClientKeyExchange是国密握手最好认的一条消息。展开它,你会看到Encrypted PreMaster Secret字段,里面的数据是SM2加密后的密文。
密文长度可以作为判断依据。SM2加密的特点是密文长度 = 明文长度 + 96字节左右(64字节的C1点坐标加32字节的C3杂凑值)。如果明文预主密钥是48字节,密文长度大约是144字节。在Wireshark的TLS记录层里,你可以看到这条消息的实际长度,对照这个特征,就算Wireshark不认识套件名,也能判断出国密密钥交换的类型。
从客户端角度讲,这条消息发出去以后,客户端就会本地派生主密钥。如果服务端没有及时回ChangeCipherSpec和Finished,大概率是SM2解密失败,常见原因包括:加密证书和服务端私钥不匹配、服务端拿到的是签名私钥而不是加密私钥。我们后面排查章节会专门说这个问题。
4.4 ChangeCipherSpec和Finished:握手成功的信号
ChangeCipherSpec是TLS里最“仪式感”的一条消息,表示从现在开始之后的消息都要用协商好的密钥加密。Wireshark里TLS记录层会显示ChangeCipherSpec,后面跟一条Finished。
Finished消息的内容是握手消息摘要,计算方式已经加密过了,在没有会话密钥的情况下看不到明文。但不需要看到明文内容,只要这个交互发生且后续没有Alert,就能确认握手成功。
生产环境抓包时,如果看到Finished之后就出现Application Data流量,说明握手完成、应用数据已经在用SM4加密了。此时可以关闭抓包,握手过程的取证工作已经完成。
4.5 国密流量能不能解密
很多人抓包后问:能不能在Wireshark里直接看到国密HTTPS的明文请求?这个问题要分情况说。
如果你有服务端的SM2签名私钥和加密私钥,理论上可以在Wireshark里配置私钥做解密。实际操作里,TLS解密需要服务的私钥(加密私钥)以及会话随机数或主密钥。Wireshark支持通过配置RSA私钥来解传统TLS,但国密TLS套件的私钥解密支持并不完善,哪怕新版Wireshark对国密套件的支持也在逐步完善中,成功率并不稳定。
我个人的建议是,优先采用“抓包看流程、端侧看结果”的思路:抓包负责确认握手流程和套件选择是否正确,业务明文是否正常,则看客户端或服务端日志。对密评来说,抓包证明国密套件真正启用,比解出明文更有意义。
5. 常见问题与排查技巧实录
抓国密包的高频问题,我整理成了一个速查表,基本覆盖我日常排障时碰到的情况。
5.1 抓不到国密套件,问题出在哪
现象:Wireshark里看到了TLS握手,但ClientHello的Cipher Suites都是常见的RSA/ECDSA套件,没有SM系列。
排查思路第一步:确认访问方式是不是真的走了国密。不少系统做的是“国密优先、国际回退”的双栈模式,普通浏览器访问默认走国际套件,只有国密浏览器或专门配置的客户端才走国密。你抓到的很可能就是国际TLS握手。
排查思路第二步:确认抓包位置是否正确。如果是本地浏览器访问测试站,抓回环接口就行;如果是访问远程服务器,一定要在服务器侧抓包,或者用交换机端口镜像。中途经过的负载均衡如果开启了SSL卸载,抓到的就是负载均衡到后端的一段内部流量,未必能看到客户端侧的真实握手。
排查思路第三步:确认你的Wireshark版本够新。老版本对国密套件支持差,很多条目不识别但数据显示在那里,把版本升级到4.x基本就能正常显示。
5.2 握手卡在ServerHelloDone,客户端不走了
现象:服务端已经发完证书和ServerHelloDone,客户端一直没有回包。
这种问题常见于证书不受信任或证书链不完整。国密浏览器对证书链校验很严格,尤其会校验签名证书和加密证书的绑定关系。如果两张证书是分开两个CA签的,而客户端只信任其中一个CA,就可能出现服务端把证书发完了,客户端在本地校验阶段直接放弃连接。
排查方式:用GmSSL的客户端命令行带着奇怪的证书路径去连,看看具体报错是哪个证书链断裂。通常输出里能看到unable to get local issuer certificate之类的信息,把这个链路补全就好了。
5.3 常见问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| ClientHello没有国密套件 | 客户端未启用国密模式 | 使用国密浏览器或配置国密客户端 |
| ServerHello返回的套件不是国密 | 服务端未正确配置国密套件 | 检查服务端TLS配置和加密套件优先级 |
| Certificate只有一张证书 | 服务端漏配加密证书/加密私钥 | 补全加密证书配置 |
| ClientKeyExchange长度异常短 | 客户端可能走了国际套件 | 重新检查套件协商结果 |
| Finished后立刻出现Alert | 密钥派生不一致或证书校验失败 | 抓一次完整流量,看Alert描述 |
| 客户端报签名验证失败 | 签名证书不可信 | 导入根证书、检查证书链 |
| 双向认证时客户端没发证书 | 客户端未配置或未选择证书 | 检查客户端证书和私钥配置 |
| Wireshark显示Unknown套件名 | 版本过旧 | 升级到Wireshark 4.x |
5.4 排查思路:先看协商、再看证书、最后查密钥
我整理了一套排查国密握手问题的标准顺序。第一步永远是看ClientHello和ServerHello里的套件选择,确认双方真的使用国密;第二步看证书链是否完整、签名证书和加密证书是否匹配;第三步才看密钥交换阶段是否出现Alert或超时。
这套顺序看起来简单,但能解决九成以上的问题。很多人一上来就盯着私钥、权限、防火墙这些外围因素,反而绕了远路。抓包排障最大的价值,就是快速把问题定位到“协商阶段”还是“证书阶段”还是“密钥交换阶段”,而不是靠猜。
另外,量产环境里做国密握手抓包时,记得同时记录一下时间戳,并且多抓几次。有些国密网关首次握手会走额外的协商流程,比如先完成TCP建链再动态下发证书,多抓几次能避免被网络层的杂讯误导。
结尾
这套国密HTTPS抓包流程,我在密评项目和国密改造里反复用过很多次。从最开始对着未知套件手足无措,到后来一眼就能从ClientHello里判断出问题,最大的体会是:国密HTTPS本质上还是TLS,只是把密码学组件整体换了一套,排障思路依然可以从协议流程入手。最后再分享一个小习惯:抓国密流量时,我会在Wireshark里同时开一个显示过滤器,把TLS握手消息类型和证书信息固定放在同一列,这样每次排查都能快速切到关键报文,不用反复翻菜单。如果你也正在做国密改造,建议先照着文章里的步骤在本地搭一次环境,把标准流程走通,再去处理真实业务中的握手异常,很多问题都会好解决得多。