​接触HTTPS也有一段时间了,对整个SSL握手的过程零零碎碎的了解了一些,趁着这篇文字系统的总结一下整个SSL握手的过程,结合Wireshark工具让自己更深刻的理解SSL通信过程。

什么是握手

​像两个人沟通一样,握手是表示一个会话的开始。对于SSL/TLS来说,通过握手建立连接,交换客户端与服务器之间的信息从而生成会话秘钥(主秘钥),用来加密之后的消息。

​在TLS中有两种主要的握手类型:一种基于RSA,一种基于Diffie-Hellman。 这两种握手类型的主要区别在于主秘钥交换和认证上。

秘钥交换身份验证
RSA握手RSARSA
DH握手DHRSA/DSA

​用RSA握手还是DH握手取决于加密套件,后面我们也会带你简单了解加密套件。现在,我们用openssl查看套件时可看到

➜ openssl ciphers -v
ECDHE-RSA-AES256-GCM-SHA384 	TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 	TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
AES256-GCM-SHA384               TLSv1.2 Kx=RSA      Au=RSA  Enc=AESGCM(256) Mac=AEAD
AES256-SHA256                   TLSv1.2 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA256
...

其中kx,au就对应了秘钥交换与身份验证。

RSA握手

说了这么多终于踏入正题。

基于RSA的TLS握手整个流程如下如所示。

通过Wireshark抓包,访问https://www.razeen.me,我们可以看到

结合上面两个流程图,我们可以了解整个流程可以分解为:

  1. 客户端向服务器发送Client Hello,告诉服务器,我支持的协议版本,加密套件等信息。

  2. 服务器收到响应,选择双方都支持的协议,套件,向客户端发送Server Hello。同时服务器也将自己的证书发送到客户端(Certificate)。

  3. 客户端自己生产预主密钥,通过公钥加密预主秘钥,将加密后的预主秘钥发送给服务器 (Client Exchange)。

  4. 服务器用自己的私钥解密加密的预主密钥。

之后,客户端与服务器用相同的算法根据客户端随机数,服务器随机数,预主秘钥生产主密钥,之后的通信将都用主密钥加密解密。

下面分别带你一一了解其中细节。

Client Hello

​点开Client Hello,我们可以看到客户端向服务器发送了哪些数据。

​在一次新的握手流程中,Client Hello 消息总数第一条消息。这条消息将客户端的功能和首选项告诉服务器。通过抓包数据,通过其字段名我们也很容易理解它的含义。

Content Type

​消息的内容类型,告诉服务器,我要握手了。

Version

​协议版本(protocol version) 告诉服务器 客户端支持的最佳协议版本。

Random

​随机数,也就是流程图中的客户端随机数。包含32字节的数据,其中28字节是随机生成的(Random Bytes)。剩下的4字节包含额外的信息(GMT Unix Time),受客户端时钟影响(一般浏览器会给他们的时间添加时钟扭曲,或者简单的发送随机4字节)。在握手的时候这随机数都是独一无二的,他们在身份验证中起到举足轻重的作用(可以防止重复攻击,并确认初始数据交换的完整性)。

Session ID

​在第一连接时,会话ID(Session ID)字段是空的,这表示客户端告诉服务器 我是新会话,没有其他会话需要恢复。在后续的连接中,这个字段可以保存会话的唯一标识。服务器可以借助会话ID在自己的缓存中找到对应的会话状态。

​典型的会话ID包含32字节的随机生成的数据,这些数据本身并没有什么价值。

Cipher Suites

​密码套件块是由客户端支持的所有密码套件组成的列表,该列表是按优先级顺序排列的。

​密码套件(cipher suite)是一组选定的加密基元和其他参数,它可以精确定义如何实现安全。套件大致由以下这些属性定义。

  • 身份验证方法
  • 密钥交换方法
  • 加密算法
  • 加密密钥大小
  • 密码模式(可应用时)
  • MAC算法(可应用时)
  • PRF(只有TLS1.2一定使用,其他版本取决于各自协议)
  • 用于Finished消息的散列函数(TLS1.2)
  • verify_data结构的长度(TLS1.2)

密码套件都倾向于使用较长的描述名称,并且相当一致:它们都是由密钥交换方法、身份验证方法、密码定义以及可选的MAC或PRF算法组合而成,如下图所示:

Compression

​客户端可以提交一个或多个支持的压缩方法。默认是null,代表没有压缩。

Extensions

​扩展块由任意数量的扩展组成。这些扩展会携带额外的数据。扩展可以在不修改协议本身的条件下为TLS协议增加功能。如果你想了解更多可以参考RFC 6066,这里不多说。

Server Hello

​当服务器收到客户端的hello消息的时候,服务器会将服务器选择的参数传送回客户端。这个消息的结构与Client Hello类似,只是每个字段只包含一个选项。

​服务器不需要支持客户端支持的最佳版本。如果服务器不支持与客户端相同的版本,可以提供某个其他版本以期待客户端能够接受。

如上图所示,服务端选择使用TLS 1.2 (version),套件使用TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。

Certificate

​从下图可以看出在服务器发送Server Hello的时候,会同时发送Certificate,Server Hello Done。

Certificate

​典型的Certificate消息用于携带X.509证书链。证书链是以ASN.1 DER编码的一系列证书,一个接一个组合而成。叶子证书必须是第一个发送,中间证书按照正确的顺序跟在叶子证书之后。根证书可以并且应该省略掉,因为在这个场景中它没有用处。

​服务器必须保证它发送的证书与选择的算法套件一致。比方说,公钥算法与套件中使用的必须匹配。除此以外,一些密钥交换算法依赖嵌入证书的特定数据,而且要求证书必须以客户端支持的算法签名。所有这些都表明服务器需要配置多个证书(每个证书可能会配备不同的证书链)。

Server Hello Done

​Sever Hello Done 消息表明服务器已经将所有预计的握手消息发送完毕。在此之后,服务器会等待客户端发送消息。

Client Key Exchange

​之后客户端向服务器发送Client Key Exchange。最后客户端与服务器互发 Change Cipher Spec,Encrypted Handshake Message。

Client Key Exchange

​Client Key Exchange 消息携带客户端为密钥交换提供的所有信息。从抓包的信息中我们可以看到,秘钥交换的主要内容是RSA Encrypted PreMaster Secret, 也就是利用证书公钥加密后的预主密钥。其中预主密钥(PreMaster Secret)是由客户端生成的48字节的随机数,RFC5426可看到其结构是这样的。

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

Change Cipher Spec

​Change Cipher Spec 消息表明发送端已取得用以生成连接参数的足够信息,已生成加密密钥(主密钥),并且将切换到加密模式。客户端和服务器在条件成熟是会发送这个消息。

​主密钥的是由预主密钥进一步计算而成,这个过程通过一个伪随机函数(pseudorandom function, PRF)来完成,这个函数可以生产任意数量的伪随机数据。其计算过程如下。

master_secret = PRF(pre_master_secret,"master secret",ClientHello.random+ServerHello.random)

Finished(Encrypted Handshake Message)

​Encrypted Handshake Message 这是由客户端服务器之间协商的算法和密钥保护的第一个消息。它意味着握手已经完成。消息内容将加密,以便双发可以安全地交换验证整个握手完整性所需要的数据。

​这个消息包含verify_data字段,它的值是握手过程中所有消息的散列值。这些消息在连接两端都按照各自所见的顺序排列,并以协商新得到的主密钥计算散列。散列函数与PRF一致,除非协商的套件指定使用其他算法。

​客户端与服务器的计算方法一致。

verify_data = PRF(master_secret,finished_label,Hash(handshake_messages))

DH握手

​下图为TLS握手采用DH算法的流程图。与RSA最大的区别在于密钥交换与身份认证上。在RSA中是由客户端发送客户端密钥交换信息完成密钥交换,通过客户端公钥加密,服务端私钥解密完成身份认证。而在DH握手过程中则略有不同。

​下图是我访问https://razeen.me 用Wireshake抓取的数据。

我们从两个图中可以看出,在DH握手过程中,多了一步Server Key Exchange。Server Key Exchange消息的目的与Client Key Exchange目的相同,都是携带密钥交换的额外数据。而在这里它带的不再是加密的预主密钥了。

也就是说它的整个流程如下:

  1. 客户端向服务器发送Client Hello,告诉服务器,我支持的协议版本,加密套件等信息。

  2. a. 服务端收到响应,选择双方都支持的协议,套件,向客户端发送Server Hello。同时服务器也将自己的证书发送到客户端(Certificate)。

    b. 服务器利用私钥将客户端随机数,服务器随机数,服务器DH参数签名,生成服务器签名。

  3. 服务端向客户端发送服务器DH参数以及服务器签名(Server Key Exchange)。

  4. 客户端向服务端发送客户端DH参数(Client Key Exchange)。

之后,客户端利用公钥验证服务器签名,客户端与服务器各自利用服务端DH参数、客户端DH参数生成预主密钥,再通过预主密钥、客户端随机数、服务端随机数生成主密钥(会话密钥)。最后握手完成,所有的消息都通过主密钥加密。

​由于DH握手过程中大部分消息格式与RSA相同,这里不再一一说明。这里主要说明一下DH的密钥交换。

DH密钥交换

​在DH密钥交换过程中主要需要的参数有6个,其中两个(dh_p和dh_g)成为域参数,由服务器选取。协商过程中,客户端和服务器各自生成另外两个参数,相互发送其中一个参数(dh_Ys和dh_Yc)到对端,经过计算,获得预主共享密钥(PreMasterSecret),我们可以先看一下DH算法的数学基础。

+--------------------------------------------------------------------+
|                    Global Pulic Elements                           |
|                                                                    |
|       dh_p                         prime number                    |
|       dh_g                         prime number, dh_g < dh_p       |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
|                    User A Key Generation                           |
|                                                                    |
|       Select private dh_Ys_a       sh_Ys_a < dh_p                  |
|       Calculate public dh_Yc_a     dh_Yc_a = dh_g^dh_Ys_a mod dh_p |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
|                    User B Key Generation                           |
|                                                                    |
|       Select private dh_Ys_b       sh_Ys_b < dh_p                  |
|       Calculate public dh_Yc_b     dh_Yc_b = dh_g^dh_Ys_b mod dh_p |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
|               Calculation of Secret Key by User A                  |
|                                                                    |
|       Secret Key premaster       premaster = dh_Yc_b^dh_Yc_a mod p |
+--------------------------------------------------------------------+
+--------------------------------------------------------------------+
|               Calculation of Secret Key by User B                  |
|                                                                    |
|       Secret Key premaster       premaster = dh_Yc_a^dh_Yc_b mod p |
+--------------------------------------------------------------------+

上面一共出现了 dh_p, dh_g, dh_Ys_a, dh_Yc_a, dh_Ys_a, dh_Yc_a, premaster 共 7 个数,其中:

  • 公开的数:dh_p, dh_g, dh_Yc_a, dh_Yc_a
  • 非公开数:dh_Ys_a, dh_Ys_b, premaster

通常情况下,dg_g 一般为 2 或 5,而 dh_p, dh_Ys_a 和 dh_Ys_b 的取值也非常大,其复杂度至少为 O(dh_p^0.5)。对于攻击者来说,已知 dh_Yc_a,dh_Ys_a 的求解非常困难,同理 dh_Ys_b 的求解也很困难,所以攻击者难以求出 premaster,所以 DH 能够保证通信双方在透明的信道中安全的交换密钥。

​在上面的介绍中你也知道,如果其中的域参数太弱,将很容易被攻破,正如2015年披露的Logjam攻击表明,512位的DH参数在使用合适的资源情况下可以被攻击者在很短的时间内成功利用。对于这些问题随后有了椭圆曲线的DH秘钥交换(ECDH),ECDH密钥交换发生在一条服务器定义的特殊椭圆曲线上,这条曲线代替了DH中的域参数,从而一定程度上提高了安全性。

总结

​到这里你理清整个握手过程了么。其实整个过程无非就是在互相交换信息,而基于RSA算法与DH算法的握手最大的区别就在于密钥交换与身份认证。前者利用客户端利用公钥加密预主密钥发送给服务端完成密钥交换,服务端利用私钥解密完成身份认证。后者利用各自发送DH参数完成密钥交换,服务器私钥签名数据,客户端公钥验签完成身份认证。

​好了就写到这里,文章中如果有错漏之处还请不吝赐教哦~

​最后,今天也是2018年的第一天,祝大家新年快乐。

参考资料

1. HTTPS 权威指南

2.SSL/TLS Perfect Forward Secrecy

3.Keyless SSL: The Nitty Gritty Technical Details

4.Announcing Keyless SSL™: All the Benefits of CloudFlare Without Having to Turn Over Your Private SSL Keys