正因种种严苛条件,TLS才够安全,因为满足这些前提条件后,真正数据传送就令人放心。除非你调动超级计算机,要不然一个TLS连接里的加密数据,你真无法破解。
但若排查工作确实需要解开密文,查看应用层信息,又该咋办?
研究TLS解密的技术要点及背后技术原理,最后实战。
1 TLS加密原理
TLS结合了对称加密和非对称加密这两大类算法的优点,密码套件是四种主要加密算法的组合。
1.1 解读TLS证书
下面这证书是访问站点 https://sharkfesteurope.wireshark.org 时获取。
站点名称跟证书名称不一致?浏览器为啥不报错?
这的站点名称跟证书实际是匹配的,但它匹配的不是Common Name,而是SAN。
TLS证书为支持更多域名,设计了扩展选项Subject Alternative Name,SAN,它就包含多个域名。比如还是这张证书,它的SAN里的域名里就有:
- wireshark.org
- sni.cloudflaressl.com
- 还有跟这次访问的站点名直接相关的*.wireshark.org。这是通配符域名,即sharkfesteurope.wireshark.org也被支持
SAN列表:
通配符证书只支持一级域名,如 *.wireshark.org
证书支持域名:
- a.wireshark.org
- b.wireshark.org
但不支持:
- a.b.wireshark.org
- a.b.c.wireshark.org
密码套件
这证书里密码套件是啥?
密钥交换算法是啥?证书里看不出,需根据握手协商的结果判定。但也可初步判断。如这次通信用TLS版本1.3,那就是DHE或ECDHE这样“前向加密”密钥交换算法。结尾E是Ephemeral,“短时间的”,即密钥是每次会话临时生成。
身份验证和签名算法
证书里明确写的ECDSA,EC就是Elliptic Curve,椭圆曲线算法,它可用更短密钥达到跟RSA同样密码强度。后面的SHA-256哈希摘要算法,证书内容用这SHA-256算法做哈希摘要,然后ECDSA算法对摘要值做签名,这样客户端就可验证这张证书的内容有没有被篡改。
对称加密算法是啥?证书这里看不出,因为它也是通过握手协商出的。用OpenSSL或者curl命令就可观察到,稍后演示。
完整性校验算法
2里面已经提过了,是SHA-256。
OpenSSL直接观测到这次TLS里协商出的密码套件:
1$ openssl s_client -connect sharkfesteurope.wireshark.org:4432......3New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA3844......
这里用的就是TLS1.3版本,密码套件TLS_AES_256_GCM_SHA384。它跟TLS1.2的那些密码套件相比还有个区别?比如跟下面这个TLS1.2密码套件比较:
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS1.3的套件 TLS_AES_256_GCM_SHA384 少俩算法:身份验证和签名算法,还有密钥交换算法。身份验证和签名算法倒是可从证书看到,ECDSA。
密钥交换算法哪去了?
因为TLS1.3只允许前向加密(PFS)的密钥交换算法,所以使用静态密钥的RSA已被排除,它默认使用DHE和ECDHE,所以就不写在密码套件名称里。
2 前向加密(PFS)
又称“完美前向加密”,Forward Secrecy和Perfect Forward Secrecy。
TLS1.3去掉了RSA,不过TLS1.3只是把RSA从密钥交换算法中排除,证书签名算法还是能用RSA。
为啥TLS1.3强制前向加密?
若密钥交换时用非前向加密算法(如RSA),一旦黑客取得服务端私钥,并抓取历史TLS密文,他就用这私钥和抓包文件,把这些TLS会话的对称密钥给还原,从而破解所有这些密文。因为可以把之前的密文都破解,RSA就不属于“前向”加密。
解决的关键: 每次参与协商对称密钥时的非对称密钥都不一样。即使黑客破解其中一次会话的密钥,也无法用这密钥破解其他会话。
案例理解
假设不断生成一个个保险箱,相当于一个个TLS加密报文,如每个箱子用同样锁,一旦其中一把锁被破解,所有保险箱都可被打开。用“前向加密”锁,每次新的保险箱都用不同锁,即使一把锁被破解,损失只是一个保险箱,其他箱仍安全。
3 TLS的软件实现
TLS只是协议,具体还得是代码。目前最流行SSL/TLS实现是OpenSSL:
- 开发库
- 也是命令行工具的名称
NSS、GnuTLS也是开源TLS实现。应用程序会基于这些TLS库来实现TLS加解密功能。
像OSI分层模型,业务代码工作在应用层,TLS库工作在表示层和会话层,两层有交互有解耦,很好协同。
来看TLS抓包解密。
4 客户端咋解密TLS?
客户端包括:
- Chrome等浏览器
- 也包括curl等命令行工具
为解密TLS,需完成前提条件:
- 创建一个存放key信息的日志文件,然后在系统 配置环境变量SSLKEYLOGFILE,值就是这文件路径
- 重启浏览器,启动抓包程序,然后访问HTTPS站点,此时TLS密钥信息将会导出到这个日志文件,而加密报文也会随着抓包,被保存到抓包文件
补充:若是Mac又不想改动全局配置,可在terminal
export SSLKEYLOGFILE=
路径,然后执行open "/Applications/Google\ Chrome.app"
,这时Chrome就继承这shell父进程的环境变量,而terminal退出后,这个环境变量就自动卸除
- Wireshark打开Preferences菜单,Protocol列表里找到TLS,把 (Pre)-Master-Secret log filename配置为那个文件的路径
之后用Wireshark打开抓包文件,就能看到解密后报文,如HTTP请求和响应,还有TLS的控制信息,都会展示为明文。
如默认看到密文:
抓包示例文件已经上传至 Gitee,结合抓包文件和文稿。
配置解密步骤后,看到明文:
很多应用层的信息都可以辅助你做排查。
背后原理是啥?
浏览器启动过程尝试读SSLKEYLOGFILE环境变量。如存在这变量,而它指向一个有效文件,浏览器就会调TLS库,让TLS库把访问HTTPS站点的过程中的key信息导出到SSLKEYLOGFILE文件:
为啥这日志文件这么强,能解密TLS?如这文件“被黑客利用”,咋办?*
5 SSLKEYLOGFILE
能解密TLS,最关键是,TLS库把密钥交换阶段的核心信息Master secret导出到这文件。基于这信息,Wireshark就可还原出当时的对称密钥,破解密文。
5.1 格式
由很多条记录组成,TLS1.2,每行是一条记录,每条记录由3部分组成,空格分隔:
<Label1> <ClientRandom1> <Secret1>
<Label2> <ClientRandom2> <Secret2>
- ……
① Label - 记录的描述
TLS1.2这值一般是CLIENT_RANDOM**。RSA也是一个可能的值,但RSA算法在密钥交换方面不是前向加密,所以不推荐使用。如你在日志文件里看到RSA,小心,说明你的TLS不是前向加密,不是很安全。
② ClientRandom-客户端生成的随机数
随机数原始长度为32字节,但在这里是用16进制表示的,每个字节用16进制表示就会成为2个字符,所以就变成了64个字符的字符串。我们在抓包文件里也能看到它,因为在密钥交换算法的设计中,ClientRandom就是要在网络上公开传输的。
③ Secret:Master secret 通过它可以生成对称密钥
Master secret固定是48字节,也是十六进制表示的关系,成为96个字节的字符串。你应该明白了,这个Master secret就是最为关键的信息了,也正是黑客苦苦寻求的东西。它是万万不能在网络上传输的,自然也不可能在抓包文件里看到它,只有TLS库才能导出它。
TLS1.3格式很不同 链接。
TLS1.2的KEYLOGFILE例子:
1CLIENT_RANDOM 770c2c73ef1ab58dda9360a94587e5f8b0a80c0b1abf628ddd7b55a118ec18ec bea2c01c5b6f9c577e8ba251c8f262adf33c5aa31a238d464a9c56dbd1bf30cf55cbf14e6175102fa1db9b8a0183a721
Gitee,可按照前面介绍的3个步骤,结合上面的抓包文件和这个日志文件,观察解密前后区别。
在输出这个key信息的时候,我们也做了对应的抓包,现在看一下抓包文件。我们选中Client Hello报文,点开TLS详情部分,继续点开TLSv1.2 Record Layer -> Handshake Protocol -> Random,你看看它是不是就是前面日志文件里,第二列的值(开头是770c)?
SSLKEYLOGFILE日志文件格式我们了解了,接下来了解Wireshark是怎么跟它协同工作,解开密文的。
6 Wireshark咋解密?
TLS1.2的SSLKEYLOGFILE中,每条记录的:
- 第一列是CLIENT_RANDOM这个字符串
- 第二列是这client random值,Wireshark就是通过它,找到对应TLS会话(可理解为TCP流)。就像上图所示,通过这个随机数,就找到这条KEYLOG记录对应的TLS会话。
接下来,Wireshark就知道真正的Master secret在哪里了:它就是前面匹配了这个客户端随机数的记录的第三列,也就是那96个字节的字符串。
由于在抓包文件里就有ECDHE密钥交换算法所需要的各种参数,结合这里的Master secret,Wireshark就可以解析出对称密钥,从而把密文解密了!
SYSKEYLOGFILE的安全性
想破解密文,既要有抓包文件,也要有SSLKEYLOGFILE日志文件,才能解密。即使都有,要是没抓到TLS握手阶段报文,还是不能解密,因为缺少客户端随机数、加密算法参数等信息,Master secret你也无法下嘴。
7 服务端如何解密TL
上面的客户端解密过程。
服务端做TLS解密,TLS是双方的加密任务,但我们一边倒关心客户端如何解密,却对服务端的解密不闻不问:
- 多数人接触的还是客户端,能在客户端解密已满足大多需求。服务端只有一部分专职运维或开发维护,关注度少
- 服务端一般有详细日志,如Nginx可向日志里输出HTTP头部和性能数据,还可输出TLS选择的密码套件等信息,这些在一般场景下也够用
- 很多服务端程序并没有提供TLS解密功能,即想做抓包解密也做不了。而要自己实现这个特性,难度跟简单的参数配置,不在一个等级。难度高
软件架构上,服务端和客户端类似,也是基于TLS库构建TLS加解密能力。
eBay软件负载均衡方案,第七层是基于 Envoy 实现。在把一套新系统搬上生产环境之前有一系列的考量,就像体检检查表逐一校验。Envoy体检有项“不合格”:Envoy不提供对TLS流量进行抓包解密功能。
Envoy是硅谷的共享出行公司Lyft于2016年发起的开源项目,可以认为是云原生时代的Nginx。
在不少情况下,这个抓包解密特性对排查很有用。像一些商业产品比如Netscaler就是可以一边抓包,一边导出TLS key。就跟我们在客户端做解密类似,我们结合这两个文件,就可以在Wireshark中既观察到TCP行为,也读到应用层信息了。
你可能会问:“Envoy可以输出Web日志呀,把各种HTTP性能指标、访问头部,甚至包括用了什么Cipher,都输出到文件,这样还不香吗?”
其实,我在开篇词和 第4讲 中都提到过网络排查的两大鸿沟。
- 应用现象跟网络现象之间的鸿沟:你可能看得懂应用层的日志,但是不知道网络上具体发生了什么。
- 工具提示跟协议理解之间的鸿沟:你看得懂 Wireshark、tcpdump 这类工具的输出信息的含义,但就是无法真正地把它们跟你对协议的理解对应起来。
而包括Envoy在内的反向代理和LB软件,虽然也都提供了应用层日志,但跟实际的网络行为还有距离,这就是“应用现象跟网络现象之间的鸿沟”:日志是日志,网络是网络。如果日志里说某个HTTP请求耗时很长,你是无法知道网络上到底什么问题导致了这么长的耗时,是丢包引起了重传?还是没有丢包,纯粹是传输速度慢呢?
为了跨越第一个鸿沟,我们选择做tcpdump抓包。但是,如果抓取到的TLS密文无法被解密,就无法知道这些究竟是应用层的什么信息,这个鸿沟依然没有被跨越。
当然,我们可以选择“妥协”,采用一些灵活的策略来开展抓包分析。比如,可以把抓包分析的重心转移到TCP和TLS本身的层面,而不再关心其承载的应用层信息。但是“隔靴搔痒”总让排查工作不是特别“痛快”,我们犹豫许久之后,还是决定把这个解密的特性实现!
这并不是一件容易的事情。不过通过调研,我们发现,其实在服务端启用跟客户端类似的TLS解密功能,技术上是可行的,其中最为关键的信息就在2017年一位Wireshark开发工程师的 演讲 中:
Applications using OpenSSL 1.1.1 or BoringSSL d28f59c27bac (2015-11-19) can be configured to dump keys: void SSLCTXsetkeylogcallback(SSL CTX ∗ctx, void (∗cb)(const SSL ∗ssl, const char ∗line));
也就是说,只要我们使用这个BoringSSL(是谷歌Fork自OpenSSL的项目)的 SSLCTXsetkeylogcallback() 回调函数,就可以把TLS信息导出来,于是我们信心大增。核心突破就是要把这个BoringSSL的回调函数给用起来。
我们需要:
- 在Envoy代码中增加调用SSLCTXsetkeylogcallback()的逻辑
- 增加了对外的接口,使得用户可以通过某种方式让Envoy知道,它需要去使用这个调用逻辑。
第二点,其实就是一种接口方式,比如SSLKEYLOGFILE环境变量就是一种,我们也可以选择API接口,或者某种别的接口。总之,只要让程序(这里是Envoy)知道:它需要去叫BoringSSL这个小弟去办点事情,整个功能就可以运作起来了。你可以参考这张示意图:
“体检通过”!我们在服务端Envoy上也可以做到方便的TLS解密了。其实,不仅实现了这个具体的需求本身,也实现了我们作为技术工作者,对“自我实现”的需求。
这个特性的主要开发者张博已经提交了PR,相信不久之后我们就能在正式版的Envoy里用上这个特性了。
实现Envoy的TLS抓包解密的具体的做法跟客户端解密的步骤差不多:
- 调用Envoy接口,启用SSL KEY导出功能。
- 做tcpdump抓包,然后把抓包文件和KEY文件复制出来。
- 在Wireshark里同样配置好 TLS协议的(Pre)-Master-Secret log filename,打开抓包文件后,就可以跟在客户端类似,直接看到明文了。
8 总结
解读一张真实的TLS证书,复习各加密算法在现实场景中的实现:
- 证书中的SAN列表包括了它所支持的站点域名,所以只要被访问的站点名称在这个列表里,名称匹配就不是问题了。
- 证书中的域名通配符只支持一级域名,而不支持二级或者更多级的域名。
- 在TLS1.3中,密钥交换算法被强制要求是前向加密算法,所以默认采用DHE和ECDHE,而RSA已经弃用。
- RSA依然可以作为可靠的身份验证和签名算法来使用。另外一种验证和签名算法是ECDSA,它可以用更短的密钥实现跟RSA同样的密码强度。
- 前向加密可以防止黑客破解发生在过去的加密流量,提供了更好的安全性。
之后就是这节课的核心了: 如何做到对抓包文件进行解密。这里又分客户端和服务端两个不同场景,你也需要重点关注。
首先,在客户端做抓包解密,需要做三件事:
- 创建一个文件,并设置为SSLKEYLOGFILE这个环境变量的值;
- 重启浏览器,开始做抓包,此时key信息被浏览器自动导入到日志文件;
- 在Wireshark里把该日志文件配置为TLS的(Pre)-Mater-Secret log filename。
这样,我们就能在Wireshark里直接读取到应用层信息了。
而在服务端抓包解密,就要依托于软件实现了,但是有些软件并没有提供这种功能,比如Envoy。借助底层BoringSSL库的接口,eBay流量管理团队实现了对这个接口的调用,我们也可以在Envoy上完成抓包解密了。
Wireshark解读密文的原理:
- 从抓包文件中定位到client random
- 从日志文件中找到同样这个client random,然后找到紧跟着的Master secret
- 用这个Master secret导出对称密钥,最后把密文解密
9 FAQ
能实时查看解密信息?
行。只要设置好:
- SSLKEYLOGFILE环境变量
- 再启动浏览器,然后直接在Wireshark里开始抓包
- 设置Wireshark的TLS协议,配置(Pre-)Master secret logfile
这时访问HTTPS站点,在Wireshark里看到的就直接是解密好的信息。因为Wireshark已经能从SSLKEYLOGFILE读到密钥信息,同时又在实时抓取到TLS密文,这种解密工作可实时进行。
为啥停止抓包后再启动抓包,抓包文件又变成密文?
重启浏览器后,Wireshark里马上就能看到HTTP数据包,确实能解密。但停止抓包后,再启动抓包,看到又变成TLS密文。重启浏览器才行。
密文是用对称密钥加密。而对称密钥的生成,是在TLS握手阶段完成的。我们前面提到过,Wireshark(也包括其他需要读取SSLKEYLOGFILE的程序)正是根据第二列的客户端随机数,来找到抓包文件中的TLS session,然后运用第三列的Master secret来获取到对称密钥的。
抓包停止后,新的HTTPS请求所触发的TLS握手就不会被抓取到。这也就意味着,Wireshark没有抓取到客户端随机数这个关键信息,尽管SSLKEYLOGFILE里依然在输出着一行行的key信息,但是Wireshark已经不知道用哪个Master secret了。自然,解密就无从做起。
而在浏览器重启后,事实上造成了TLS的重新握手,此时就又可以抓取到客户端随机数了,这样,解密工作就可以恢复。你看,这其实跟 第3讲 中,没抓到TCP握手报文就无法知道Window Scale参数这个问题差不多,也是关于握手的,只不过这次是TLS握手。“技术是相通的”,这句话真不是随便说说。
DH、DHE、ECDHE,这三者的联系和区别是啥?
DH、DHE和ECDHE都是用于加密通信中的密钥交换算法。它们的联系是都是通过非对称加密来实现密钥交换,从而确保加密通信的安全性。
区别在于它们所使用的算法不同。DH是基于离散对数问题的算法,DHE是DH算法的一种实现,其中E表示"ephemeral",即临时的。ECDHE则是基于椭圆曲线离散对数问题的算法。
总体来说,ECDHE相对于DH和DHE来说更加安全且效率更高,因此在现代加密通信中被广泛应用。
浏览器会根据SSLKEYLOGFILE这个环境变量,把key信息导出到相应的文件,那么curl也会读取这个变量并导出key信息吗?
是的,curl也可以读取SSLKEYLOGFILE环境变量并将key信息导出到指定的文件中。在使用curl时,可以设置该环境变量来启用key信息的导出功能,例如:
1export SSLKEYLOGFILE=/path/to/ssl_key.log2curl https://example.com
这将会将key信息导出到/path/to/ssl_key.log
文件中。需要注意的是,只有启用了TLS调试模式的curl才能够导出key信息。可以使用-v
选项来启用调试模式,例如:
1curl -v https://example.com
这将会打印出TLS握手过程中的详细信息,包括key信息。