在互联网的上古时代,大部分协议都是不安全的,其中就包括统治当今互联网的HTTP协议。HTTP协议直接明文传输ASCII码,是窃听和篡改的极好材料。在ARP攻击等等MIMT攻击成功之后,任何用HTTP协议传输的数据都会成为攻击者的待宰羔羊。

更糟的是,如果我们不信任ISP,那么数据的每一次路由都会产生危险。这不只是搞个弹窗吓唬人的问题,随着互联网的发展,越来越多的服务要依赖敏感信息的传输。如果没有某种程度上的加密,那么这些服务便无法成立。基于很多类似的理由,SSL/TLS应运而生。

SSL是一家公司的产品,SSLv1从未公开过,SSLv2有严重漏洞,SSLv3是SSL的最后一个版本,其后,它被IETF收编为了TLSv1.0,随后又有TLSv1.1,TLSv1.2,目前(2019年10月)TLSv1.3还未大规模部署。这篇文章主要介绍TLSv1.2。

TLS,也就是传输层安全,是一个三层上的协议,但它的下层一般是TCP协议(当然其实也可以是UDP协议),上层则一般是HTTP协议。读者可能认为TLS只是用作HTTPS的加密层,实际上它的上层可以是任何协议,只不过现今多用于http协议。(这就是为什么DNS over HTTPS和DNS over TLS是不一样的)它在安全方面有三个主要目的:

  1. 对服务器和客户端的数据进行端对端加密,使得任何中间节点无法理解数据,防止窃听。
  2. 通过证书的引入,验证服务端(有时也验证客户端)的身份。
  3. 通过校验的引入,保证数据在传输过程中未被篡改。

探索TLS,就是一遍遍的问自己“TLS如何做到这三点的”。首先来解决第一个问题:

TLS如何保证数据是加密的?如何保证在攻击者监听了这个TCP请求的全过程时,攻击者仍然无法理解数据?

好吧,让我来做一次监听者。用浏览器打开https://cnnic.com.cn,在Wireshark中抓包,看一下这个连接的全貌:

一次TLS连接

在WINDOWS下抓包每一个己方的包都会被捕捉两次,应该是wireshark的捕获有些问题,先不管这些,看一下这个连接:

前三个包是TCP握手,不用多说,第四个包叫做Client Hello,这是啥呢?

随便查一下资料,发现这是tls握手的第一个包。看来,要窃听数据,需要先学习一下tls握手的过程。

注:这图对于加密信息和未加密信息的划分是错误的

看上去有点复杂,实际上也有点复杂。不过好消息是,当我们理解了握手过程后,我们就理解了数据是如何加密的。

只能硬着头皮上了!

第一个包叫做Client Hello,这是干什么的呢?用wireshark看看有啥吧。

哇!一个handshake头里竟然有这么多内容,要学到什么时候才能掌握啊?但又有一个好消息,下面都是“Extension”,在大部分时候,extension都是不用重点关注的。不然为啥不放在不是扩展的参数里面呢?那就先看看上面的字段。

1.Handshake Type,这不用多说,就是来标识握手进入哪一阶段的。

2.Length 这也不用多说。

3.Version, 不用多说。

4.Random,听名字就知道这是一个随机数,而且显然是客户端产生的。它是干什么的呢?先按下不表。

5.Session ID Length 和 Session ID,这和对话恢复有关,按下不表。

6. Cipher Suites Length 和 Cipher Suites,这似乎到了重点了!这长度这么长(34个字节),看看都有啥:

34 = 17 * 2,看来每一个选项都有两个字节(废话,图上都是四个十六进制数,可不就是两个字节吗),这Cipher的意思,是密钥,密钥套装,岂不就是加密方式套装……仔细看看RFC5246,你会发现这选项的原型是TLS_NULL_WITH_NULL_NULL,看来,每个选项都是分为三个部分的。比如说,

TLS_RSA_WITH_AES_256_CBC_SHA

肯定就是表示RSA和什么 什么。但是后面的怎么划分呢?不用着急,求助一下搜索引擎,

哈哈,这样的话,肯定就是AES-256-CBC 和 SHA了!

那新的问题就来了,为什么要发这么多呢?聪明的你肯定猜到了。这种要商量用啥的协议经常是一方宣示自己可以用啥,另一方在一方的宣示中选一个。是不是客户端在宣示自己可以用这17种方式,然后让服务端选一个呢?是不是呢?让我们看看第二个包 Server Hello吧!

Bingo!服务端只回复了一种方式,果然是在客户端提供的17种方式中选择了一种。可以看到这个包和上一个的头部没啥太大差别,只不过扩展少了很多,随机数变了而已。

下一个包是什么?是TLS的核心:Certificate。这证书可不是一般的东西,但我们留到下一篇文章研究。现在要知道的是,这证书里有一个宝贝:客户端的公钥。公钥是什么?咳咳,如果这个也不知道,就请自行查询吧。

那么在证书发送完成后,服务端(如果采用RSA)就会发送一个Server Hello Done,表示我的事情已经做完了,下面看你(客户端)表演了。

客户端在收到这个包(之前没有收到要求客户端的证书的请求)后,会发送一个Client Key Exchange。这个包,毫不夸张地说,是握手中最关键的一个包。先看看里面有什么:

emmm……这么简单就说是关键,不脸红吗?还真不脸红,因为生成加密的密钥靠的就是这个。

下面开始我的半吊子密码学秀。

首先,上面的AES-256-CBC是啥呢?求助一下维基:

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

看来也就是一种对称加密算法。对称加密也就是说有一个密钥就可以搞定加解密。那看来256就是说,密钥是256位的。这时就可以联想到有个说法说,为了性能,TLS加密实际上采用的对称加密算法,但是对称加密的密钥是非对称加密的。AES-256-CBC恐怕就是那个“对称加密算法”了。

这个Pre-Master就是256位的,会不会它就是密码呢?即使是,也应该是用公钥加密后的吧。

咦?似乎得到答案了呢!客户端生成一个对称加密的密钥,加密以后送给服务端,依据非对称加密的特性,这岂不就只有服务端可以解密?

这是比较接近的答案。但是,TLS并不是这么做的。查看一下RFC 5246:

struct {
 ProtocolVersion client_version;
 opaque random[46];
 } PreMasterSecret;

看来这实际上是版本+随机数。那么这个东西是怎么产生密钥的呢?

再看RFC 5246:

master_secret = PRF(pre_master_secret, "master secret",
 ClientHello.random + ServerHello.random)[0..47];
key_block = PRF(SecurityParameters.master_secret,
 "key expansion",
 SecurityParameters.server_random +
 SecurityParameters.client_random);
client_write_MAC_key[SecurityParameters.mac_key_length]
 server_write_MAC_key[SecurityParameters.mac_key_length]
 client_write_key[SecurityParameters.enc_key_length]
 server_write_key[SecurityParameters.enc_key_length]
 client_write_IV[SecurityParameters.fixed_iv_length]
 server_write_IV[SecurityParameters.fixed_iv_length]

不知道你什么感觉,但是我看到这个的时候,感觉只有一个:这都是什么?PRF是什么?master_key是什么?最后一堆东西又是什么?

好。我们先来看PRF,查询一下维基,你会发现甚至没有中文版:

In cryptography, a pseudorandom function family, abbreviated PRF, is a collection of efficiently-computable functions which emulate a random oracle in the following way: no efficient algorithm can distinguish (with significant advantage) between a function chosen randomly from the PRF family and a random oracle (a function whose outputs are fixed completely at random). Pseudorandom functions are vital tools in the construction of cryptographic primitives, especially secure encryption schemes.

硬着头皮看完了,我觉得还不是很懂,这是一“族”输出随机数的函数吗?幸好RFC 5246里有一个PRF的定义:

P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
 HMAC_hash(secret, A(2) + seed) +
 HMAC_hash(secret, A(3) + seed) + ...
PRF(secret, label, seed) = P_<hash>(secret, label + seed)

看到这里我终于有点懂了。首先这个HMAC_hash函数是“带密钥的hash函数”。也就是说,如果密钥不同,hash的结果一定不同,其实也可以想成受两个参数影响的hash函数。然后这个A(n)是递归定义的:

A() is defined as:

A(0) = seed

A(i) = HMAC_hash(secret, A(i-1))

(以上+表示连接,比如说a+b = ab)

这个P_hash函数其实是很简单的。因为HMAC_hash函数只能生成定长的输出,要想得到变长的输出,就要想点办法(像P_hash一样去构造)。P_hash就是“变长的HMAC_hash的函数”。这里举个例子,比如说HMAC_hash的输出是48位的,你想要一个100位的,那就让N = 3,最后得到144位输出,然后取前100位就好啦。(当然,严格来说P_hash的输出长度只能是HMAC_hash函数的整倍数)

理解了P_hash,下面的PRF就水到渠成了。因为这个PRF只是把自己的第二和第三个参数连接起来,再把自己的第一个参数和连接起来的东西作为P_hash的参数。

这样我们就知道了PRF是个什么东西,它确实是一个伪随机生成函数,而且生成的长度是不定的。

回到那三组代码:

master_secret = PRF(pre_master_secret, "master secret",
 ClientHello.random + ServerHello.random)[0..47];

这是说,master_key是P_hash(pre_master_secret, “master secret”+ClientHello.random + ServerHello.random)的输出取前48位。至于这两个random,还记得我们分析Client Hello 和 Server Hello时的情况吗?

然后下面的事情就更简单了,我们把master_key作为P_hash的第一个参数,生成一个符合要求长度的key_block,把这个key_block顺序分为6组(字面意思),生成下面的东西:

client_write_MAC_key[SecurityParameters.mac_key_length]
 server_write_MAC_key[SecurityParameters.mac_key_length]
 client_write_key[SecurityParameters.enc_key_length]
 server_write_key[SecurityParameters.enc_key_length]
 client_write_IV[SecurityParameters.fixed_iv_length]
 server_write_IV[SecurityParameters.fixed_iv_length]

名字里都带key了,肯定就是加解密用的了。具体每一个是什么作用,这里不再多说。

注意,上面的运算,是服务器和客户端同时都在做的。所以他们共享同一组密钥。

现在是不是皆大欢喜了呢?似乎还有一个问题,那个TLS_RSA_WITH_AES_256_CBC_SHA 最后的 SHA是什么呢?恐怕读者知道这应该是哈希算法吧,注意到HMAC_hash,嗯?

但实际上,这个SHA指的是HMAC-SHA1,它被用作MAC算法,而PRF使用的HMAC算法则由协议版本决定,比如tls1.2是HMAC-SHA256。

好了好了,说了这么多,简单总结一下 RSA_WITH_AES_256_CBC_SHA 加密的核心:

客户端产生一个随机数作为pre_master_key,将它用服务端公钥加密后送给服务端,然后客户端和服务端同时用基于HMAC-SHA的PRF产生master_key,再用master_key和随机数作为PRF的输入得到六个密钥,用于AES-256-CBC加密。以后的通信,都用AES-256-CBC加密,具有了反窃听的特性。只要私钥是安全的,就只能有客户端和服务端进行解密。

发表评论

电子邮件地址不会被公开。 必填项已用*标注