You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

26 KiB

20 | TLS加解密如何解密HTTPS流量

你好,我是胜辉。

在上节课里我们对TLS的整体的知识体系做了总览性的介绍然后回顾了两个实际的案例从中领略了TLS握手的奥妙。我们也知道了TLS握手的信息量还是很大的稍有差池就可能引发问题。我们只有对这些知识有深刻的理解才能更准确地展开排查。

不过也正因为这种种严苛的条件TLS才足够安全因为满足了这些前提条件后真正的数据传送就令人十分放心了。除非你能调动超级计算机或者拥有三体人的智慧要不然一个TLS连接里面的加密数据你是真的没有办法破解的。

可话说回来,如果排查工作确实需要我们解开密文,查看应用层信息,那我们又该如何做到呢?

所以在这节课里我会带你学习TLS解密的技术要点以及背后的技术原理最后进行实战演练让加密不再神秘。好了让我们开始吧。

TLS加密原理

在上节课里我们已经了解到TLS是结合了对称加密和非对称加密这两大类算法的优点而密码套件是四种主要加密算法的组合。那么这些概念跟我们的日常工作又有着什么样的交集呢

解读TLS证书

下面这个证书,是我在访问站点https://sharkfesteurope.wireshark.org的时候获取到的我们来仔细读一下这里面的内容看看哪些是跟我们学过的TLS知识相关的。

我把图中的很多关键信息做了标记,希望可以帮助你更好地理解。

从上到下,我们了解了这张证书所在的证书链,然后是证书名称、身份验证和签名算法、有效期。不过,看完这个证书,你可能也发现了一个小问题:站点名称跟证书名称不一致?这两个不匹配,浏览器为啥不报错呢?

图片

其实这里的站点名称跟证书实际上是匹配的但它匹配的不是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里协商出来的密码套件

$ openssl s_client -connect sharkfesteurope.wireshark.org:443
......
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
......

原来这里用的就是最新的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所以就不写在密码套件名称里了。

那么在这里,就又涉及了一个新的概念:前向加密。

前向加密PFS

前向加密又称为“完美前向加密”它的英文就是Forward Secrecy和Perfect Forward Secrecy。

虽然我前面刚提到TLS1.3去掉了RSA不过你不要误会了TLS1.3只是把RSA从密钥交换算法中排除了但证书签名算法还是可以用RSA的。

**为什么TLS1.3强制要求前向加密呢?**这是因为如果在密钥交换的时候用非前向加密的算法比如RSA那么一旦黑客取得了服务端私钥并且抓取了历史上的TLS密文他就可以用这个私钥和抓包文件把这些TLS会话的对称密钥给还原出来从而破解所有这些密文。因为可以把之前的密文都破解RSA就不属于“前向”加密。

人们发现,要解决这个问题的关键,就要做到:每次参与协商对称密钥时的非对称密钥都不一样。这样的话,即使黑客破解了其中一次会话的密钥,也无法用这个密钥破解其他会话。

我们可以用一个例子来帮助理解“前向加密”。假设我们不断地生成一个个的保险箱相当于一个个的TLS加密报文如果每个箱子用同样的锁那么一旦其中一把锁被破解所有的保险箱都可以被打开了。用上“前向加密”锁之后每次新的保险箱都用不同的锁那么即使一把锁被破解损失的只是一个保险箱其他的箱子依旧安全。

TLS的软件实现

TLS只是一套协议主要是“动动嘴皮子”具体的活当然还是代码来干。目前应用最为广泛的SSL/TLS实现可能就是OpenSSL了它既是一个开发库也是一个命令行工具的名称。另外NSS和GnuTLS也是开源的TLS实现。应用程序会基于这些TLS库来实现TLS加解密功能。

有没有觉得这个很像OSI的分层模型业务代码工作在应用层TLS库工作在表示层和会话层两层之间有交互也有解耦起到了很好的协同的效果。

学习完TLS加密原理我们就要进入动手环节了也就是期待已久的TLS抓包解密让秘密不再是秘密。

客户端如何做TLS解密

这里说的客户端包括了Chrome、Firefox等浏览器也包括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然后又不免担心如果这个文件“被坏人利用了”,该怎么办?

所以我们还需要近距离地认识一下SSLKEYLOGFILE。

SSLKEYLOGFILE

这个文件之所以能够解密TLS最关键的是TLS库把密钥交换阶段的核心信息Master secret导出到了这个文件中。基于这个信息Wireshark就可以还原出当时的对称密钥从而破解密文。

我们先来认识一下SSLKEYLOGFILE的格式。它是由很多条记录组成的对于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的具体的例子

CLIENT_RANDOM 770c2c73ef1ab58dda9360a94587e5f8b0a80c0b1abf628ddd7b55a118ec18ec bea2c01c5b6f9c577e8ba251c8f262adf33c5aa31a238d464a9c56dbd1bf30cf55cbf14e6175102fa1db9b8a0183a721

补充:这个日志文件也已经上传至Gitee你可以按照前面介绍的3个步骤结合上面的抓包文件和这个日志文件自己来观察解密前后的区别。

在输出这个key信息的时候我们也做了对应的抓包现在看一下抓包文件。我们选中Client Hello报文点开TLS详情部分继续点开TLSv1.2 Record Layer -> Handshake Protocol -> Random你看看它是不是就是前面日志文件里第二列的值开头是770c

图片

SSLKEYLOGFILE日志文件格式我们了解了接下来了解Wireshark是怎么跟它协同工作解开密文的。

Wireshark是怎么解开密文的

在TLS1.2的SSLKEYLOGFILE中每条记录的第一列是CLIENT_RANDOM这个字符串第二列是这个client random的值Wireshark就是通过它找到对应的TLS会话你可以理解为是TCP流。就像上图所示通过这个随机数就找到了这条KEYLOG记录对应的TLS会话了。

图片

那么接下来Wireshark就知道真正的Master secret在哪里了它就是前面匹配了这个客户端随机数的记录的第三列也就是那96个字节的字符串。

图片

由于在抓包文件里就有ECDHE密钥交换算法所需要的各种参数结合这里的Master secretWireshark就可以解析出对称密钥从而把密文解密了

图片

SYSKEYLOGFILE的安全性

现在回答一下前面的问题:如果这个文件“被坏人利用了”,怎么办?

通过前面的学习我们知道了要想破解密文既要有抓包文件也要有SSLKEYLOGFILE日志文件两者结合才能解密。而且不要忘了即使你有抓包文件和日志文件要是没抓到TLS握手阶段的报文也还是不能解密因为缺少了客户端随机数、加密算法参数等信息Master secret对你也是无法下嘴的美食。

服务端如何做TLS解密

其实上面的客户端做解密的过程网络上已经有很多资料了但是接下来要介绍的“服务端做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,打开抓包文件后,就可以跟在客户端类似,直接看到明文了。

几个问题

然后到这里,这里我们还需要搞清楚几个问题。

问题1我想实时查看解密信息行不行

一个字的答案行。只要设置好前面提及的3件事

  • SSLKEYLOGFILE环境变量
  • 之后再启动浏览器然后直接在Wireshark里开始抓包
  • 设置Wireshark的TLS协议配置(Pre-)Master secret logfile。

这时候你访问HTTPS站点时在Wireshark里看到的就直接是解密好的信息了因为Wireshark已经能从SSLKEYLOGFILE里读取到密钥信息同时又在实时地抓取到TLS密文这种解密工作是可以实时进行的。

当然,这里还有一个小的注意点,我们在第二个问题里展开。

问题2为什么停止抓包后再启动抓包抓包文件又变成密文了

有同学就遇到这个问题重启浏览器后在Wireshark里马上就能看到HTTP数据包确实能解密。但是停止抓包之后再启动抓包看到的又变成了TLS密文了。必须得重启浏览器才行。这是为何呢

表面上看,这似乎又是一个“重启大法”的问题,但本质上呢?

我们知道密文是用对称密钥加密的。而对称密钥的生成是在TLS握手阶段完成的。我们前面提到过Wireshark也包括其他需要读取SSLKEYLOGFILE的程序正是根据第二列的客户端随机数来找到抓包文件中的TLS session然后运用第三列的Master secret来获取到对称密钥的。

抓包停止后新的HTTPS请求所触发的TLS握手就不会被抓取到。这也就意味着Wireshark没有抓取到客户端随机数这个关键信息尽管SSLKEYLOGFILE里依然在输出着一行行的key信息但是Wireshark已经不知道用哪个Master secret了。自然解密就无从做起。

而在浏览器重启后事实上造成了TLS的重新握手此时就又可以抓取到客户端随机数了这样解密工作就可以恢复。你看这其实跟第3讲没抓到TCP握手报文就无法知道Window Scale参数这个问题差不多也是关于握手的只不过这次是TLS握手。“技术是相通的”这句话真不是随便说说。

小结

这节课我们通过对一张真实的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导出对称密钥最后把密文解密。

思考题

最后,给你留两道思考题:

  • DH、DHE、ECDHE这三者的联系和区别是什么呢
  • 浏览器会根据SSLKEYLOGFILE这个环境变量把key信息导出到相应的文件那么curl也会读取这个变量并导出key信息吗

欢迎你把答案分享到留言区,我们一起交流、进步。