# 应用层之HTTPS协议

`Https(Hyper Text Transfer Protocol over Secure Socket Layer)`，是一种基于`SSL/TLS`的`Http`，所有的`http`数据都是在`SSL/TLS`协议封装之上传输的。`Https`协议在`Http`协议的基础上，添加了`SSL/TLS`握手以及数据加密传输，也属于应用层协议。所以，研究`Https`协议原理，最终其实是研究`SSL/TLS`协议。

## :pencil2: `SSL/TLS` 协议

`SSL`协议，是一种安全传输协议，最初是由`Netscape`在1996年发布，由于一些安全的原因`SSL v1.0`和`SSL v2.0`都没有公开，直到1996年的`SSL v3.0`。`TLS`是`SSL v3.0`的升级版，1999年，互联网标准化组织`ISOC`接替`NetScape`公司发布了`TLS 1.0`版。2006年和2008年，进行了两次升级，分别为`TLS 1.1`版和`TLS 1.2`版，最新的变动是2011年`TLS 1.2`的修订版。目前市面上所有的`Https`都是用的是`TLS`，而不是`SSL`。

从协议内部的功能层面上来看，`SSL/TLS` 协议可分为两层：

1.`SSL/TLS` 记录协议（`SSL/TLS Record Protocol`），它建立在可靠的传输层协议（如 `TCP`）之上，为上层协议提供数据封装、压缩、加密等基本功能。

2.`SSL/TLS` 握手协议（`SSL/TLS Handshake Protocol`），它建立在 `SSL/TLS` 记录协议之上，用于在实际的数据传输开始前，通讯双方进行身份认证、协商加密算法、交换加密密钥等初始化协商功能。

从协议使用方式来看，又可以分成两种类型：

1.`SSL/TLS` 单向认证，就是用户到服务器之间只存在单方面的认证，即客户端会认证服务器端身份，而服务器端不会去对客户端身份进行验证。首先，客户端发起握手请求，服务器收到握手请求后，会选择适合双方的协议版本和加密方式。然后，再将协商的结果和服务器端的公钥一起发送给客户端。客户端利用服务器端的公钥，对要发送的数据进行加密，并发送给服务器端。服务器端收到后，会用本地私钥对收到的客户端加密数据进行解密。然后，通讯双方都会使用这些数据来产生双方之间通讯的加密密钥。接下来，双方就可以开始安全通讯过程了。

2.`SSL/TLS` 双向认证，就是双方都会互相认证，也就是两者之间将会交换证书。基本的过程和单向认证完全一样，只是在协商阶段多了几个步骤。在服务器端将协商的结果和服务器端的公钥一起发送给客户端后，会请求客户端的证书，客户端则会将证书发送给服务器端。然后，在客户端给服务器端发送加密数据后，客户端会将私钥生成的数字签名发送给服务器端。而服务器端则会用客户端证书中的公钥来验证数字签名的合法性。建立握手之后过程则和单向通讯完全保持一致。

## :pencil2: `TLS`握手

`TLS`的握手阶段是发生在`TCP`握手之后。握手实际上是一种协商的过程，对协议所必需的一些参数进行协商。`TLS`握手过程分为四步，过程如下：（备注：图中加方括号的均为可选消息）

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLivMhS5rUtOo1FtkW%2F-MCLk1NU5mogGUiIXwzN%2F46.png?alt=media\&token=d09f9633-96f2-4f59-bd2e-7505e85b3338)

#### **1、Client Hello**

由于客户端(如浏览器)对一些加解密算法的支持程度不一样，但是在`TLS`协议传输过程中必须使用同一套加解密算法才能保证数据能够正常的加解密。在`TLS`握手阶段，客户端首先要告知服务端，自己支持哪些加密算法，所以客户端需要将本地支持的加密套件(`Cipher Suite`)的列表传送给服务端。除此之外，客户端还要产生一个随机数，这个随机数一方面需要在客户端保存，另一方面需要传送给服务端，客户端的随机数需要跟服务端产生的随机数结合起来产生`Master Secret`。

#### **2、Server Hello**

上图中，从`Server Hello`到`Server Done`，有些服务端的实现是每条单独发送，有服务端实现是合并到一起发送。`Sever Hello`和`Server Done`都是只有头没有内容的数据。

服务端在接收到客户端的`Client Hello`之后，服务端需要将自己的证书发送给客户端。这个证书是对于服务端的一种认证。例如，客户端收到了一个来自于称自己是`www.alipay.com`的数据，但是如何证明对方是合法的`alipay`支付宝呢？这就是证书的作用，支付宝的证书可以证明它是`alipay`，而不是财付通。证书是需要申请，并由专门的数字证书认证机构(CA)通过非常严格的审核之后颁发的电子证书。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存，不可泄漏。公钥则是附带在证书的信息中，可以公开的。证书本身也附带一个证书电子签名，这个签名用来验证证书的完整性和真实性，可以防止证书被串改。另外，证书还有个有效期。

在服务端向客户端发送的证书中没有提供足够的信息的时候，还可以向客户端发送一个`Server Key Exchange`。

此外，对于非常重要的保密数据，服务端还需要对客户端进行验证，以保证数据传送给了安全的合法的客户端。服务端可以向客户端发出`Cerficate Request`消息，要求客户端发送证书对客户端的合法性进行验证。跟客户端一样，服务端也需要产生一个随机数发送给客户端。客户端和服务端都需要使用这两个随机数来产生`Master Secret`。

最后服务端会发送一个`Server Hello Done`消息给客户端，表示`Server Hello`消息结束了。

#### **3、Client Key Exchange**

如果服务端需要对客户端进行验证，在客户端收到服务端的`Server Hello`消息之后，首先需要向服务端发送客户端的证书，让服务端来验证客户端的合法性。

在此之前的所有`TLS`握手信息都是明文传送的。在收到服务端的证书等信息之后，客户端会使用一些加密算法(例如：`RSA, Diffie-Hellman`)产生一个48个字节的`Key`，这个`Key`叫`PreMaster Secret`，很多材料上也被称作`PreMaster Key`，最终通过`Master secret`生成`session secret`， `session secret`就是用来对应用数据进行加解密的。`PreMaster secret`属于一个保密的Key，只要截获`PreMaster secret`，就可以通过之前明文传送的随机数，最终计算出`session secret`，所以`PreMaster secret`使用`RSA`非对称加密的方式，使用服务端传过来的公钥进行加密，然后传给服务端。

接着，客户端需要对服务端的证书进行检查，检查证书的完整性以及证书跟服务端域名是否吻合。

`ChangeCipherSpec`是一个独立的协议，体现在数据包中就是一个字节的数据，用于告知服务端，客户端已经切换到之前协商好的加密套件的状态，准备使用之前协商好的加密套件加密数据并传输了。在`ChangecipherSpec`传输完毕之后，客户端会使用之前协商好的加密套件和session secret加密一段`Finish`的数据传送给服务端，此数据是为了在正式传输应用数据之前对刚刚握手建立起来的加解密通道进行验证。

#### **4、Server Finish**

服务端在接收到客户端传过来的`PreMaster`加密数据之后，使用私钥对这段加密数据进行解密，并对数据进行验证，也会使用跟客户端同样的方式生成`session secret`，一切准备好之后，会给客户端发送一个`ChangeCipherSpec`，告知客户端已经切换到协商过的加密套件状态，准备使用加密套件和`session secret`加密数据了。之后，服务端也会使用`session secret`加密后一段`Finish`消息发送给客户端，以验证之前通过握手建立起来的加解密通道是否成功。

根据之前的握手信息，如果客户端和服务端都能对`Finish`信息进行正常加解密且消息正确的被验证，则说明握手通道已经建立成功，接下来，双方可以使用上面产生的`session secret`对数据进行加密传输了。

> 1. 服务器也可以要求验证客户端，即双向认证，可以在过程2要发送`client_certificate_request` 信息，客户端在过程4中先发送`client_certificate`与`certificate_verify_message`信息，证书的验证方式基本相同，`certificate_verify_message`是采用client的私钥加密的一段基于已经协商的通信信息得到数据，服务器可以采用对应的公钥解密并验证；
> 2. 根据使用的密钥交换算法的不同，如 `ECC` 等，协商细节略有不同，总体相似；
> 3. `sever key exchange`的作用是`server certificate`没有携带足够的信息时，发送给客户端以计算`pre-master`，如基于 DH 的证书，公钥不被证书中包含，需要单独发送；
> 4. `change cipher spec` 实际可用于通知对端改版当前使用的加密通信方式，当前没有深入解析；
> 5. `alter message` 用于指明在握手或通信过程中的状态改变或错误信息，一般告警信息触发条件是连接关闭，收到不合法的信息，信息解密失败，用户取消操作等，收到告警信息之后，通信会被断开或者由接收方决定是否断开连接。&#x20;

## :pencil2: Secret Keys

![Secret Keys的的生成过程以及作用流程图](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLivMhS5rUtOo1FtkW%2F-MCM3IrOLtNCCW2bPVXj%2F47.png?alt=media\&token=3534a4ac-50b9-4eb7-a8ed-ab23bddba8e7)

#### `PreMaster secret`&#x20;

`PreMaster secret`是在客户端使用`RSA`或者`Diffie-Hellman`等加密算法生成的。它将用来跟服务端和客户端在`Hello`阶段产生的随机数结合在一起生成`Master secret`。在客户端使用服务单的公钥对`PreMaster secret`进行加密之后传送给服务端，服务端将使用私钥进行解密得到`PreMaster secret`。也就是说服务端和客户端都有一份相同的`PreMaster secret`和随机数。

`PreMaster secret`前两个字节是`TLS`的版本号，这是一个比较重要的用来核对握手数据的版本号，因为在`Client Hello`阶段，客户端会发送一份加密套件列表和当前支持的`SSL/TLS`的版本号给服务端，而且是使用明文传送的，如果握手的数据包被破解之后，攻击者很有可能串改数据包，选择一个安全性较低的加密套件和版本给服务端，从而对数据进行破解。所以，服务端需要对密文中解密出来对的`PreMaster`版本号跟之前`Client Hello`阶段的版本号进行对比，如果版本号变低，则说明被串改，则立即停止发送任何消息。

#### `Master secret`&#x20;

上面已经提到，由于服务端和客户端都有一份相同的`PreMaster secret`和随机数，这个随机数将作为后面产生`Master secret`的种子，结合`PreMaster secret`，客户端和服务端将计算出同样的`Master secret`。

`Master secret`是有系列的`hash`值组成的，它将作为数据加解密相关的`secret`的`Key Material`。`Master secret`最终解析出来的数据如下：

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLivMhS5rUtOo1FtkW%2F-MCM3fiL4kywfrwgfsru%2F48.png?alt=media\&token=ef7407d4-8ac9-4e63-aa65-5cb573b7fc25)

其中，`write MAC key`，就是`session secret`或者说是`session key`。`Client write MAC key`是客户端发数据的`session secret`，`Server write MAC secret`是服务端发送数据的`session key`。`MAC(Message Authentication Code)`，是一个数字签名，用来验证数据的完整性，可以检测到数据是否被串改。

## :pencil2: session的恢复

握手阶段用来建立`SSL`连接。如果出于某种原因，对话中断，就需要重新握手。

这时有两种方法可以恢复原来的`session`：一种叫做`session ID`，另一种叫做`session ticket`。这是`TLS` 协议的两类会话缓存机制。

`session ID`的思想很简单，就是每一次对话都有一个编号（`session ID`）。如果对话中断，下次重连的时候，只要客户端给出这个编号，且服务器有这个编号的记录，双方就可以重新使用已有的"对话密钥"，而不必重新生成一把。

`session ID`是目前所有浏览器都支持的方法，但是它的缺点在于`session ID`往往只保留在一台服务器上。所以，如果客户端的请求发到另一台服务器，就无法恢复对话。`session ticket`就是为了解决这个问题而诞生的，目前只有Firefox和Chrome浏览器支持。`session ticket`是加密的，只有服务器才能解密，其中包括本次对话的主要信息，比如对话密钥和加密方法。当服务器收到`session ticket`以后，解密后就不必重新生成对话密钥了。

### 1、会话标识 session ID&#x20;

1. 如果客户端和服务器之间曾经建立了连接，服务器会在握手成功后返回 `session ID`，并保存对应的通信参数在服务器中；
2. 如果客户端再次需要和该服务器建立连接，则在 `client_hello` 中 `session ID` 中携带记录的信息，发送给服务器；
3. 服务器根据收到的 `session ID` 检索缓存记录，如果没有检索到货缓存过期，则按照正常的握手过程进行；
4. 如果检索到对应的缓存记录，则返回 `change_cipher_spec` 与 `encrypted_handshake_message` 信息，两个信息作用类似，`encrypted_handshake_message` 是到当前的通信参数与 `master_secret`的`hash` 值；
5. &#x20;如果客户端能够验证通过服务器加密数据，则客户端同样发送 `change_cipher_spec` 与 `encrypted_handshake_message` 信息；
6. 服务器验证数据通过，则握手建立成功，开始进行正常的加密数据通信。&#x20;

### 2、会话记录 session ticket&#x20;

1. 如果客户端和服务器之间曾经建立了连接，服务器会在 `new_session_ticket` 数据中携带加密的 `session_ticket` 信息，客户端保存；
2. 如果客户端再次需要和该服务器建立连接，则在 `client_hello` 中扩展字段 `session_ticket` 中携带加密信息，一起发送给服务器；
3. 服务器解密 `sesssion_ticket` 数据，如果能够解密失败，则按照正常的握手过程进行；
4. 如果解密成功，则返回 `change_cipher_spec` 与 `encrypted_handshake_message` 信息，两个信息作用与 `session ID` 中类似；
5. 如果客户端能够验证通过服务器加密数据，则客户端同样发送 `change_cipher_spec`与`encrypted_handshake_message` 信息；
6. 服务器验证数据通过，则握手建立成功，开始进行正常的加密数据通信。

## :pencil2: 重建连接

重建连接 `renegotiation` 即放弃正在使用的 `TLS` 连接，从新进行身份认证和密钥协商的过程，特点是不需要断开当前的数据传输就可以重新身份认证、更新密钥或算法，因此服务器端存储和缓存的信息都可以保持。

### 1、服务器重建连接

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCM4MB4Memc-Ah0ZsEV%2F-MCMQzbFTZ-bWqzY2VDU%2F49.png?alt=media\&token=990446cd-d193-44ad-b252-df9f1f118521)

服务器端重建连接一般情况是客户端访问受保护的数据时发生。基本过程如下：&#x20;

1. 客户端和服务器之间建立了有效 `TLS` 连接并通信；
2. 客户端访问受保护的信息；
3. 服务器端返回 `hello_request` 信息；
4. 客户端收到 `hello_request` 信息之后发送 `client_hello` 信息，开始重新建立连接。

### 2、 **客户端重建连接**

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCM4MB4Memc-Ah0ZsEV%2F-MCMR6N9goTbLm5S_Er4%2F50.png?alt=media\&token=4863ba65-0f86-43d6-b556-b1d385820b97)

客户端重建连接一般是为了更新通信密钥。&#x20;

1. 客户端和服务器之间建立了有效 `TLS` 连接并通&#x4FE1;**；**
2. 客户端需要更新密钥，主动发出 `client_hello` 信息；
3. 服务器端收到 `client_hello` 信息之后无法立即识别出该信息非应用数据，因此会提交给下一步处理，处理完之后会返回通知该信息为要求重建连接；
4. 在确定重建连接之前，服务器不会立即停止向客户端发送数据，可能恰好同时或有缓存数据需要发送给客户端，但是客户端不会再发送任何信息给服务器；
5. 服务器识别出重建连接请求之后，发送 `server_hello` 信息至客户端；
6. 客户端也同样无法立即判断出该信息非应用数据，同样提交给下一步处理，处理之后会返回通知该信息为要求重建连接；
7. 客户端和服务器开始新的重建连接的过程。&#x20;

## :pencil2: `HTTPS`性能损耗&#x20;

前文讨论了`HTTPS`原理与优势：身份验证、信息加密与完整性校验等，且未对`TCP`和`HTTP`协议做任何修改。但通过增加新协议以实现更安全的通信必然需要付出代价，`HTTPS`协议的性能损耗主要体现如下：&#x20;

1. 增加延时 ：分析前面的握手过程，一次完整的握手至少需要两端依次来回两次通信，至少增加延时 $$2\times RTT$$ ，利用会话缓存从而复用连接，延时也至少 $$1\times RTT$$ 。&#x20;
2. 消耗较多的CPU资源 ：除数据传输之外，`HTTPS`通信主要包括对对称加解密、非对称加解密(服务器主要采用私钥解密数据)，`RSA`的解密能力是困扰`HTTPS`接入的主要难题。&#x20;

## :pencil2: `HTTPS`接入优化&#x20;

#### 1、`CDN`接入&#x20;

`HTTPS` 增加的延时主要是传输延时 `RTT`，`RTT` 的特点是节点越近延时越小，`CDN(Content Delivery Network)` 天然离用户最近，因此选择使用 `CDN` 作为 `HTTPS` 接入的入口，将能够极大减少接入延时。`CDN` 节点通过和业务服务器维持长连接、会话复用和链路质量优化等可控方法，极大减少 `HTTPS` 带来的延时。&#x20;

#### 2、会话缓存&#x20;

虽然前文提到 `HTTPS` 即使采用会话缓存也要至少`1*RTT`的延时，但是至少延时已经减少为原来的一半，明显的延时优化；同时，基于会话缓存建立的 `HTTPS` 连接不需要服务器使用`RSA`私钥解密获取 `Pre-master` 信息，可以省去CPU 的消耗。如果业务访问连接集中，缓存命中率高，则`HTTPS`的接入能力讲明显提升。

#### 3、硬件加速&#x20;

为接入服务器安装专用的`SSL`硬件加速卡，作用类似 GPU，释放 CPU，能够具有更高的 `HTTPS` 接入能力且不影响业务程序的。

#### 4、远程解密&#x20;

本地接入消耗过多的 CPU 资源，浪费了网卡和硬盘等资源，考虑将最消耗 CPU 资源的`RSA`解密计算任务转移到其它服务器，如此则可以充分发挥服务器的接入能力，充分利用带宽与网卡资源。远程解密服务器可以选择 CPU 负载较低的机器充当，实现机器资源复用，也可以是专门优化的高计算性能的服务器。当前 `CDN` 是用于大规模`HTTPS`接入的解决方案之一。&#x20;

#### 5、`SPDY/HTTP2`&#x20;

前面的方法分别从减少传输延时和单机负载的方法提高 `HTTPS` 接入性能，但是方法都基于不改变 HTTP 协议的基础上提出的优化方法，`SPDY/HTTP2` 利用 `TLS/SSL` 带来的优势，通过修改协议的方法来提升 `HTTPS` 的性能，提高下载速度等。&#x20;

## `Https`与`Http`的区别

1、`https`协议需要到CA申请证书，一般免费证书较少，因而需要一定费用。

2、`http`是超文本传输协议，信息是明文传输，`https`则是具有安全性的`ssl/tls`加密传输协议。

3、`http`和`https`使用的是完全不同的连接方式，用的端口也不一样，前者是80，后者是443。

4、`http`的连接很简单，是无状态的；`HTTPS`协议是由`SSL/TLS+HTTP`协议构建的可进行加密传输、身份认证的网络协议，比`http`协议安全。

## 客户端在使用`HTTPS`方式与Web服务器通信时的步骤

1. 客户使用`https`的URL访问Web服务器，要求与Web服务器建立`SSL`连接。
2. Web服务器收到客户端请求后，会将网站的证书信息（证书中包含公钥）传送一份给客户端。
3. 客户端的浏览器与Web服务器开始协商`SSL/TLS`连接的安全等级，也就是信息加密的等级。
4. 客户端的浏览器根据双方同意的安全等级，建立会话密钥，然后利用网站的公钥将会话密钥加密，并传送给网站。
5. Web服务器利用自己的私钥解密出会话密钥。
6. Web服务器利用会话密钥加密与客户端之间的通信。

CA证书的相关内容见[网络与安全](https://mqjyl2012.gitbook.io/backend-development/network-communication-and-programming/network-security)部分。
