Loading

11-应用层-HTTPs

1. Quick Start

HTTPS(HyperText Transfer Protocol over Secure Socket Layer)超文本传输安全协议

1.1 与 HTTP 的区别?

  • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议(Transport Layer Security Protocol,传输层安全协议),使得报文能够加密传输;
  • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输;
  • 两者的默认端口不⼀样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443;
  • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

HTTPS 解决了 HTTP 的哪些问题?

HTTP 由于是明文传输,所以安全上存在 3 个风险:窃听风险、篡改风险、冒充风险。HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决了上述的风险:信息加密、校验机制、身份证书

HTTPS 是如何解决上面的三个风险的?

  • 混合加密的方式实现信息的机密性,解决了窃听的风险;
  • 摘要算法的方式来实现完整性,它能够为数据生成独一无二的「摘要」,摘要用于校验数据的完整性,解决了篡改的风险;
  • 将服务器公钥放入到数字证书中,解决了冒充的风险。

a. 混合加密

通过混合加密的方式可以保证信息的机密性,解决了窃听的风险。

HTTPS 采用的是对称加密非对称加密结合的「混合加密」方式:

  • 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
  • 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。

采用「混合加密」的方式的原因:

  • 对称加密只使用 1 个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
  • 非对称加密使用 2 个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。

b. 摘要算法+数字签名

为了保证传输的内容不被篡改,我们需要对内容计算出一个「摘要」,然后同内容一起传输给对方。

对方收到后,先是对内容也计算出一个「摘要」,然后跟发送方发送的「摘要」做一个比较,如果「摘要」相同,说明内容没有被篡改,否则就可以判断出内容被篡改了。

那么,在计算机里会用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的「摘要」,这个哈希值是唯一的,且无法通过哈希值推导出内容

通过哈希算法可以确保内容不会被篡改,但是并不能保证「内容 + 哈希值」不会被中间人替换,因为这里缺少对客户端收到的消息是否来源于服务端的证明

消息摘要算法分为三类:

  • MD(Message Digest,消息摘要算法)
  • SHA(Secure Hash Algorithm,安全散列算法)
  • MAC(Message Authentication Code,消息认证码)

那为了避免这种情况,计算机里会用非对称加密算法来解决,共有两个密钥:

  1. 一个是公钥,这个是可以公开给所有人的;
  2. 一个是私钥,这个必须由本人管理,不可泄露。

这两个密钥可以双向加解密的,比如可以用公钥加密内容,然后用私钥解密,也可以用私钥加密内容,公钥解密内容。流程的不同,意味着目的也不相同:

  • 公钥加密,私钥解密。这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;
  • 私钥加密,公钥解密。这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。

一般我们不会用非对称加密来加密实际的传输内容,因为非对称加密的计算比较耗费性能的。

所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密

私钥是由服务端保管,然后服务端会向客户端颁发对应的公钥。如果客户端收到的信息,能被公钥解密,就说明该消息是由服务器发送的。

【小结】TLS 的功能实现主要依赖于 3 类基本算法:散列函数 Hash、对称加密、非对称加密。其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密通讯,基于散列函数验证信息的完整性。

c. 数字证书

前面我们知道:

  • 可以通过哈希算法来保证消息的完整性;
  • 可以通过数字签名来保证消息的来源可靠性(能确认消息是由持有私钥的一方发送的);

但是这还远远不够,还缺少身份验证的环节,万一公钥是被伪造的呢?


举个例子,你想向老师请假,一般来说是要求由家长写一份请假理由并签名,老师才能允许你请假。虽然你妈妈持有私钥,老师通过是否能用公钥解密来确认这个请假条是不是来源你母亲的。但是我们还可以自己伪造出一对公私钥啊!

你找了个夜晚,偷偷把老师桌面上和你妈妈配对的公钥,换成了你的公钥,那么下次你在请假的时候,你继续模仿你妈妈的字迹写了个请假条,然后用你的私钥做个了「数字签名」。

但是老师并不知道自己的公钥被你替换过了,所以她还是按照往常一样用公钥解密,由于这个公钥和你的私钥是配对的,老师当然能用这个被替换的公钥解密出来,并且确认了内容的完整性,于是老师就会以为是你母亲写的请假条,又允许你请假了。

正所谓魔高一丈,道高一尺。

既然伪造公私钥那么随意,所以你妈把她的公钥注册到警察局,警察局用她们自己的私钥对你母亲的公钥做了个数字签名,然后把你妈妈的「个人信息 + 公钥 + 数字签名」打包成一个数字证书,也就是说这个数字证书包含你妈妈的公钥。

这样,你妈妈如果因为家里确实有事要向老师帮你请假的时候,不仅会用自己的私钥对内容进行签名,还会把数字证书给到老师。

老师拿到了数字证书后,首先会去警察局验证这个数字证书是否合法,因为数字证书里有警察局的数字签名,警察局要验证证书合法性的时候,用自己的公钥解密,如果能解密成功,就说明这个数字证书是在警察局注册过的,就认为该数字证书是合法的,然后就会把数字证书里头的公钥(你妈妈的)给到老师。

由于通过警察局验证了数字证书是合法的,那么就能证明这个公钥就是你母亲的,于是老师就可以安心的用这个公钥解密出请假条,如果能解密出,就证明是你妈妈写的请假条。

正是通过了一个权威的机构来证明你妈妈的身份,所以你的伪造公私钥这个小伎俩就没用了。


在计算机里,这个权威的机构就是 CA (数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。

通过数字证书的方式保证服务器公钥的身份,解决冒充的风险。

1.2 如何建立连接的?

SSL/TLS 协议基本流程:

  1. 客户端向服务器索要并验证服务器的公钥。
  2. 双方协商生产「会话秘钥」。
  3. 双方采用「会话秘钥」进行加密通信。

前两步也就是 SSL/TLS 的建立过程,也就是 TLS 握手阶段。

TLS 的「握手阶段」涉及四次通信,使用不同的密钥交换算法,TLS 握手流程也会不一样的,现在常用的密钥交换算法有两种:RSA 算法和 ECDHE 算法。

基于 RSA 算法的 TLS 握手过程比较容易理解(明文 → 非对称加密 → 对称加密),所以这里先用这个给大家展示 TLS 握手过程,如下图:

a. ClientHello

首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。

在这一步,客户端主要向服务器发送以下信息:

  • 客户端支持的 TLS 协议版本,如 TLS 1.2 版本;
  • 客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一;
  • 客户端支持的密码套件列表,如 RSA 加密算法;

b. SeverHello

服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:

  • 确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信;
  • 服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一;
  • 确认的密码套件列表,如 RSA 加密算法;
  • 服务器的数字证书;

c. ClientResp

客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。

如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:

  • 一个随机数(pre-master key)。该随机数会被服务器公钥加密;
  • 加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信;
  • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验;

上面第一项的随机数是整个握手阶段的第 3 个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。

服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」

d. ServerResp

服务器收到客户端的第 3 个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。

然后,向客户端发送最后的信息:

  • 加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信;
  • 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验;

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

1.3 如何校验数字证书?

接下来,详细说一下实际中数字证书签发验证流程:

CA 签发证书的过程,如上图左边部分:

  • 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
  • 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
  • 最后将 Certificate Signature 添加在文件证书上,形成数字证书;

客户端校验服务端的数字证书的过程,如上图右边部分:

  • 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
  • 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;
  • 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。

但事实上,证书的验证过程中还存在一个证书信任链的问题,因为我们向 CA 申请的证书一般不是根证书签发的,而是由中间证书签发的,比如百度的证书,从下图你可以看到,证书的层级有三级:

对于这种三级层级关系的证书的验证过程如下:

  1. 客户端收到 baidu.com 的证书后,发现这个证书的签发者不是根证书,就无法根据本地已有的根证书中的公钥去验证 baidu.com 证书是否可信。于是,客户端根据 baidu.com 证书中的签发者,找到该证书的颁发机构是 “GlobalSign Organization Validation CA - SHA256 - G2”,然后向 CA 请求该中间证书。
  2. 请求到证书后发现 “GlobalSign Organization Validation CA - SHA256 - G2” 证书是由 “GlobalSign Root CA” 签发的,由于 “GlobalSign Root CA” 没有再上级签发机构,说明它是根证书,也就是自签证书。应用软件会检查此证书有否已预载于根证书清单上,如果有,则可以利用根证书中的公钥去验证 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,如果发现验证通过,就认为该中间证书是可信的。
  3. “GlobalSign Organization Validation CA - SHA256 - G2” 证书被信任后,可以使用 “GlobalSign Organization Validation CA - SHA256 - G2” 证书中的公钥去验证 baidu.com 证书的可信性,如果验证通过,就可以信任 baidu.com 证书。

在这 3 个步骤中,最开始客户端只信任根证书 GlobalSign Root CA 证书的,然后 “GlobalSign Root CA” 证书信任 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,而 “GlobalSign Organization Validation CA - SHA256 - G2” 证书又信任 baidu.com 证书,于是客户端也信任 baidu.com 证书。

总括来说,由于用户信任 GlobalSign,所以由 GlobalSign 所担保的 baidu.com 可以被信任,另外由于用户信任操作系统或浏览器的软件商,所以由软件商预载了根证书的 GlobalSign 都可被信任。

操作系统里一般都会内置一些根证书,比如我的电脑里内置的根证书有这么多:

这样的一层层地验证就构成了一条信任链路,整个证书信任链验证流程如下图所示:

最后一个问题,为什么需要证书链这么麻烦的流程?Root CA 为什么不直接颁发证书,而是要搞那么多中间层级呢?

这是为了确保根证书的绝对安全性,将根证书隔离地越严格越好,不然根证书如果失守了,那么整个信任链都会有问题。

1.4 如何保证数据完整性?

TLS 在实现上分为握手层记录层两层。

  • TLS 记录协议(TLS Record protocol)
    • TLS 记录协议位于 TLS 握手协议的下层,是负责使用对称密码对消息进行加密通信的部分;
    • 加密使用的密钥是通过 TLS 握手协议(TLS Handshaking Protocols)在服务器和客户端之间协商决定的;
    • 负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;
  • TLS 握手层·协议
    • TLS 握手协议(TLS Handshaking Protocols)就是我们前面说的 TLS 四次握手的过程,负责协商加密算法和生成对称密钥,后续用此密钥来保护应用程序数据(即 HTTP 数据);
    • 密码规格变更协议(TLS Change Spec Protocol)负责向通信对象传达变更密码方式的信号;
    • 警告协议(TLS Alert Protocol)是 TLS 握手协议负责在发送错误时将错误传达给对方;

TLS 记录协议主要负责消息(HTTP 数据)的压缩,加密及数据的认证,过程如下图:

具体过程如下:

  • 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩;
  • 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码;
  • 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密码进行加密;
  • 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。

记录协议完成后,最终的报文数据将传递到传输控制协议层(TCP)进行传输。

1.5 HTTPS一定安全可靠吗?

【问题场景】客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站」转发到了一个「中间人服务器」,于是客户端是和「中间人服务器」完成了 TLS 握手,然后这个「中间人服务器」再与真正的服务端完成 TLS 握手。

具体过程如下:

  • 「客户端」向「服务端」发起 HTTPS 建立连接请求时,然后被「假基站」转发到了一个「中间人服务器」,接着「中间人」向「服务端」发起 HTTPS 建立连接请求,此时「客户端」与「中间人」进行 TLS 握手,「中间人」与「服务端」进行 TLS 握手;
  • 在「客户端」与「中间人」进行 TLS 握手过程中,中间人会发送自己的公钥证书给客户端,「客户端」验证证书的真伪,然后从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给「中间人」,「中间人」使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(A),后续「客户端」与「中间人」通信就用这个对称加密密钥来加密数据了。
  • 在「中间人」与「服务端」进行 TLS 握手过程中,「服务端」会发送从 CA 机构签发的公钥证书给「中间人」,从证书拿到公钥,并生成一个随机数,用公钥加密随机数发送给「服务端」,「服务端」使用私钥解密,得到随机数,此时双方都有随机数,然后通过算法生成对称加密密钥(B),后续「中间人」与「服务端」通信就用这个对称加密密钥来加密数据了。
  • 后续的通信过程中,「中间人」用对称加密密钥(A)解密「客户端」的 HTTPS 请求的数据,然后用对称加密密钥(B)加密 HTTPS 请求后,转发给「服务端」,接着「服务端」发送 HTTPS 响应数据给「中间人」,「中间人」用对称加密密钥(B)解密 HTTPS 响应数据,然后再用对称加密密钥(A)加密后,转发给「客户端」。

从客户端的角度看,其实并不知道网络中存在中间人服务器这个角色。那么中间人就可以解开浏览器发起的 HTTPS 请求里的数据,也可以解开服务端响应给浏览器的 HTTPS 响应数据。相当于,中间人能够 “偷看” 浏览器与服务端之间的 HTTPS 请求和响应的数据。

但是要发生这种场景是有前提的,前提是用户点击接受了中间人服务器的证书。

中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。

如果用户执意点击「继续浏览此网站」,相当于用户接受了中间人伪造的证书,那么后续整个 HTTPS 通信都能被中间人监听了。所以,这其实并不能说 HTTPS 不够安全,毕竟浏览器都已经提示证书有问题了,如果用户坚决要访问,那不能怪 HTTPS ,得怪自己手贱。

另外,如果你的电脑中毒了,被恶意导入了中间人的根证书,那么在验证中间人的证书的时候,由于你操作系统信任了中间人的根证书,那么等同于中间人的证书是合法的,这种情况下,浏览器是不会弹出证书存在问题的风险提醒的。这其实也不关 HTTPS 的事情,是你电脑中毒了才导致 HTTPS 数据被中间人劫持的。

所以,HTTPS 协议本身到目前为止还是没有任何漏洞的,即使你成功进行中间人攻击,本质上是利用了客户端的漏洞(用户点击继续访问或者被恶意导入伪造的根证书),并不是 HTTPS 不够安全

为什么抓包工具能截取 HTTPS 数据?

很多抓包工具 之所以可以明文看到 HTTPS 数据,工作原理与中间人一致的。

对于 HTTPS 连接来说,中间人要满足以下两点,才能实现真正的明文代理:

  1. 中间人,作为客户端与真实服务端建立连接这一步不会有问题,因为服务端不会校验客户端的身份;
  2. 中间人,作为服务端与真实客户端建立连接,这里会有客户端信任服务端的问题,也就是服务端必须有对应域名的私钥;

中间人要拿到私钥只能通过如下方式:

  1. 去网站服务端拿到私钥;
  2. 去 CA 拿域名签发私钥;
  3. 自己签发证书,且要被浏览器信任;

不用解释,抓包工具只能使用第三种方式取得中间人的身份。

使用抓包工具进行 HTTPS 抓包的时候,需要在客户端安装 Fiddler 的根证书,这里实际上起认证中心(CA)的作用。

抓包工具能够抓包的关键是客户端会往系统受信任的根证书列表中导入抓包工具生成的证书,而这个证书会被浏览器信任,也就是抓包工具给自己创建了一个认证中心 CA,客户端拿着中间人签发的证书去中间人自己的 CA 去认证,当然认为这个证书是有效的。

如何避免被中间人抓取数据?

我们要保证自己电脑的安全,不要被病毒乘虚而入,而且也不要点击任何证书非法的网站,这样 HTTPS 数据就不会被中间人截取到了。

当然,我们还可以通过 HTTPS 双向认证来避免这种问题。

一般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份。

如果用了双向认证方式,不仅客户端会验证服务端的身份,而且服务端也会验证客户端的身份。服务端一旦验证到请求自己的客户端为不可信任的,服务端就拒绝继续通信,客户端如果发现服务端为不可信任的,那么也中止通信。

2. HTTPS RSA 握手解析

2.1 TLS 握手过程

HTTP 由于是明文传输,所谓的明文,就是说客户端与服务端通信的信息都是肉眼可见的,随意使用一个抓包工具都可以截获通信的内容。所以安全上存在以下三个风险:窃听风险、篡改风险、冒充风险。

HTTPS 在 HTTP 与 TCP 层之间加入了 TLS 协议,来解决上述的风险。TLS 协议是如何解决 HTTP 的风险的呢?

  • 信息加密: HTTP 交互信息是被加密的,第三方就无法被窃取;
  • 校验机制:校验信息传输过程中是否有被第三方篡改过,如果被篡改过,则会有警告提示;
  • 身份证书:证明淘宝是真的淘宝网;

可见,有了 TLS 协议,能保证 HTTP 通信是安全的了,那么在进行 HTTP 通信前,需要先进行 TLS 握手。TLS 的握手过程,如下图:

上图简要概述了 TLS 的握手过程,其中每一个「框」都是一个记录(record),记录是 TLS 收发数据的基本单位,类似于 TCP 里的 Segment。多个记录可以组合成一个 TCP 包发送,所以通常经过「四个消息」就可以完成 TLS 握手,也就是需要 2个 RTT 的时延,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。

所以可以发现,HTTPS 是应用层协议,需要先完成 TCP 连接建立,然后走 TLS 握手过程后,才能建立通信安全的连接。事实上,不同的密钥交换算法,TLS 的握手过程可能会有一些区别。

这里先简单介绍下密钥交换算法,因为考虑到性能的问题,所以双方在加密应用信息时使用的是对称加密密钥,而对称加密密钥是不能被泄漏的,为了保证对称加密密钥的安全性,所以使用非对称加密的方式来保护对称加密密钥的协商,这个工作就是密钥交换算法负责的。

接下来,我们就以最简单的 RSA 密钥交换算法,来看看它的 TLS 握手过程。

2.2 RSA 握手过程

传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的,在将 TLS 证书部署服务端时,证书文件其实就是服务端的公钥,会在 TLS 握手阶段传递给客户端,而服务端的私钥则一直留在服务端,一定要确保私钥不能被窃取。

在 RSA 密钥协商算法中,客户端会生成随机密钥,并使用服务端的公钥加密后再传给服务端。根据非对称加密算法,公钥加密的消息仅能通过私钥解密,这样服务端解密后,双方就得到了相同的密钥,再用它加密应用消息。

我用 Wireshark 工具抓了用 RSA 密钥交换的 TLS 握手过程,你可以从下面看到,一共经历了 4 次握手:

对应 Wireshark 的抓包,我也画了一幅图,你可以从下图很清晰地看到该过程:

那么,接下来针对每一个 TLS 握手做进一步的介绍。

a. TLS 第 1 次握手

客户端首先会发一个「Client Hello」消息,字面意思我们也能理解到,这是跟服务器「打招呼」。

消息里面有客户端使用的 TLS 版本号、支持的密码套件列表,以及生成的随机数(Client Random),这个随机数会被服务端保留,它是生成对称加密密钥的材料之一。

b. TLS 第 2 次握手

当服务端收到客户端的「Client Hello」消息后,会确认 TLS 版本号是否支持,和从密码套件列表中选择一个密码套件,以及生成随机数(Server Random)

接着,返回「Server Hello」消息,消息里面有服务器确认的 TLS 版本号,也给出了随机数(Client Random),然后从客户端的密码套件列表选择了一个合适的密码套件。

可以看到,服务端选择的密码套件是 “Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256”。

这个密码套件看起来真让人头晕,好一大串,但是其实它是有固定格式和规范的。基本的形式是「密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法」, 一般 WITH 单词前面有两个单词,第一个单词是约定密钥交换的算法,第二个单词是约定证书的验证算法。比如刚才的密码套件的意思就是:

  • 由于 WITH 单词只有一个 RSA,则说明握手时密钥交换算法和签名算法都是使用 RSA;
  • 握手后的通信使用 AES 对称算法,密钥长度 128 位,分组模式是 GCM;
  • 摘要算法 SHA256 用于消息认证和产生随机数;

就前面这两个客户端和服务端相互「打招呼」的过程,客户端和服务端就已确认了 TLS 版本和使用的密码套件,而且你可能发现客户端和服务端都会各自生成一个随机数,并且还会把随机数传递给对方。

那这个随机数有啥用呢?其实这两个随机数是后续作为生成「会话密钥」的条件,所谓的会话密钥就是数据传输时,所使用的对称加密密钥。

然后,服务端为了证明自己的身份,会发送「Server Certificate」给客户端,这个消息里含有数字证书。

随后,服务端发了「Server Hello Done」消息,目的是告诉客户端,我已经把该给你的东西都给你了,本次打招呼完毕。

【补充】客户端拿到了服务端的数字证书后,要怎么校验该数字证书是真实有效的呢?→ #1.3

c. TLS 第 3 次握手

客户端验证完证书后,认为可信则继续往下走。

接着,客户端就会生成一个新的随机数 (pre-master),用服务器的 RSA 公钥加密该随机数,通过「Client Key Exchange」消息传给服务端。

服务端收到后,用 RSA 私钥解密,得到客户端发来的随机数(pre-master)。

至此,客户端和服务端双方都共享了 3 个随机数,分别是 Client Random、Server Random、pre-master

于是,双方根据已经得到的三个随机数,生成会话密钥(Master Secret),它是对称密钥,用于对后续的 HTTP 请求/响应的数据加解密。

生成完「会话密钥」后,然后客户端发一个「Change Cipher Spec」,告诉服务端开始使用加密方式发送消息。

然后,客户端再发一个「Encrypted Handshake Message(Finishd)」消息,把之前所有发送的数据做个摘要,再用会话密钥(master secret)加密一下,让服务器做个验证,验证加密通信「是否可用」和「之前握手信息是否有被中途篡改过」。

可以发现,「Change Cipher Spec」之前传输的 TLS 握手数据都是明文,之后都是对称密钥加密的密文。

d. TLS 第 4 次握手

服务器也是同样的操作,发「Change Cipher Spec」和「Encrypted Handshake Message」消息,如果双方都验证加密和解密没问题,那么握手正式完成。

最后,就用「会话密钥」加解密 HTTP 请求和响应了。

2.3 RSA 算法的缺陷

使用 RSA 密钥协商算法的最大问题是不支持〈前向保密〉

在密码学中,PFS(Perfect Forward Secrecy)是安全通信协议的一个属性。PFS 保护过去的会话以免在未来秘钥或密码的(泄露)造成的危害。因为对手或恶意方会活跃地干扰,导致长期秘钥(long-term secret keys)或者密码在未来可能被泄露,但是如果使用了 PFS,即使秘钥被泄露,过去的加密的通信和会话记录也不会被恢复和解密。

因为客户端传递随机数(用于生成对称加密密钥的条件之一)给服务端时使用的是公钥加密的,服务端收到后,会用私钥解密得到随机数。所以一旦服务端的私钥泄漏了,过去被第三方截获的所有 TLS 通讯密文都会被破解。

为了解决这个问题,后面就出现了 ECDHE 密钥协商算法,我们现在大多数网站使用的正是 ECDHE 密钥协商算法。

3. HTTPS ECDHE 握手解析

3.1 离散对数

ECDHE 密钥协商算法是 DH 算法演进过来的,所以我们先从 DH 算法说起。DH 算法是非对称加密算法, 因此它可以用于密钥交换,该算法的核心数学思想是离散对数

离散对数是「离散 + 对数」的两个数学概念的组合,所以我们先来复习一遍对数。要说起对数,必然要说指数,因为它们是互为反函数,指数就是幂运算,对数是指数的逆运算。

对数运算的取值是可以连续的,而离散对数的取值是不能连续的,因此也以「离散」得名。离散对数是在对数运算的基础上加了「模运算」,也就说取余数,对应编程语言的操作符是「%」,也可以用 mod 表示。离散对数的概念如下图:

上图的,底数 a 和模数 p 是离散对数的公共参数,也就说是公开的,b 是真数,i 是对数。知道了对数,就可以用上面的公式计算出真数。但反过来,知道真数却很难推算出对数。特别是当模数 p 是一个很大的质数,即使知道底数 a 和真数 b ,在现有的计算机的计算水平是几乎无法算出离散对数的,这就是 DH 算法的数学基础。

3.2 DH 算法

认识了离散对数,我们来看看 DH 算法是如何密钥交换的。

现假设小红和小明约定使用 DH 算法来交换密钥,那么基于离散对数,小红和小明需要先确定〈模数〉和〈底数〉作为算法的参数,这两个参数是公开的,用 P 和 G 来代称。

然后小红和小明各自生成一个随机整数作为私钥,双方的私钥要各自严格保管,不能泄漏,小红的私钥用 a 代称,小明的私钥用 b 代称。

现在小红和小明双方都有了 P 和 G 以及各自的私钥,于是就可以计算出公钥

  • 小红的公钥记作 A,A = G ^ a ( mod P );
  • 小明的公钥记作 B,B = G ^ b ( mod P );

A 和 B 也是公开的,因为根据离散对数的原理,从真数(A 和 B)反向计算对数 a 和 b 是非常困难的,至少在现有计算机的计算能力是无法破解的,如果量子计算机出来了,那就有可能被破解,当然如果量子计算机真的出来了,那么密钥协商算法就要做大的升级了。

双方交换各自 DH 公钥后,小红手上共有 5 个数:P、G、a、A、B,小明手上也同样共有 5 个数:P、G、b、B、A。然后小红执行运算: B ^ a ( mod P ),其结果为 K,因为离散对数的幂运算有交换律,所以小明执行运算: A ^ b ( mod P ),得到的结果也是 K。

这个 K 就是小红和小明之间用的对称加密密钥,可以作为会话密钥使用。

可以看到,整个密钥协商过程中,小红和小明公开了 4 个信息:P、G、A、B,其中 P、G 是算法的参数,A 和 B 是公钥,而 a、b 是双方各自保管的私钥,黑客无法获取这 2 个私钥,因此黑客只能从公开的 P、G、A、B 入手,计算出离散对数(私钥)。

前面也多次强调, 根据离散对数的原理,如果 P 是一个大数,在现有的计算机的计算能力是很难破解出 私钥 a、b 的,破解不出私钥,也就无法计算出会话密钥,因此 DH 密钥交换是安全的。

3.3 DHE 算法

根据私钥生成的方式,DH 算法分为两种实现:

  • static DH 算法(废弃)
  • DHE 算法(常用)

static DH 算法里有一方的私钥是静态的,也就说每次密钥协商的时候有一方的私钥都是一样的,一般是服务器方固定,即 a 不变,客户端的私钥则是随机生成的。

于是,DH 交换密钥时就只有客户端的公钥是变化,而服务端公钥是不变的,那么随着时间延长,黑客就会截获海量的密钥协商过程的数据,因为密钥协商的过程有些数据是公开的,黑客就可以依据这些数据暴力破解出服务器的私钥,然后就可以计算出会话密钥了,于是之前截获的加密数据会被破解,所以 static DH 算法不具备前向安全性

既然固定一方的私钥有被破解的风险,那么干脆就让双方的私钥在每次密钥交换通信时,都是随机生成的、临时的,这个方式也就是 DHE 算法,E 全称是 ephemeral(临时性的)。

所以,即使有个牛逼的黑客破解了某一次通信过程的私钥,其他通信过程的私钥仍然是安全的,因为每个通信过程的私钥都是没有任何关系的,都是独立的,这样就保证了「前向安全」

3.4 ECDHE 算法

DHE 算法由于计算性能不佳,因为需要做大量的乘法,为了提升 DHE 算法的性能,所以就出现了现在广泛用于密钥交换算法 —— ECDHE 算法

ECDHE 算法是在 DHE 算法的基础上利用了 ECC 椭圆曲线特性,可以用更少的计算量计算出公钥,以及最终的会话密钥。

小红和小明使用 ECDHE 密钥交换算法的过程:

  • 双方事先确定好使用哪种椭圆曲线,和曲线上的基点 G,这两个参数都是公开的;
  • 双方各自随机生成一个随机数作为私钥d,并与基点 G相乘得到公钥Q(Q = dG),此时小红的公私钥为 Q1 和 d1,小明的公私钥为 Q2 和 d2;
  • 双方交换各自的公钥,最后小红计算点(x1,y1) = d1Q2,小明计算点(x2,y2) = d2Q1,由于椭圆曲线上是可以满足乘法交换和结合律,所以 d1Q2 = d1d2G = d2d1G = d2Q1 ,因此双方的 x 坐标是一样的,所以它是共享密钥,也就是会话密钥

这个过程中,双方的私钥都是随机、临时生成的,都是不公开的,即使根据公开的信息(椭圆曲线、公钥、基点 G)也是很难计算出椭圆曲线上的离散对数(私钥)。

3.5 ECDHE 握手算法

知道了 ECDHE 算法基本原理后,我们就结合实际的情况来看看。

我用 Wireshark 工具抓了用 ECDHE 密钥协商算法的 TSL 握手过程,可以看到是四次握手:

细心的小伙伴应该发现了,使用了 ECDHE,在 TLS 第四次握手前,客户端就已经发送了加密的 HTTP 数据,而对于 RSA 握手过程,必须要完成 TLS 四次握手,才能传输应用数据。

所以,ECDHE 相比 RSA 握手过程省去了一个消息往返的时间,这个有点「抢跑」的意思,它被称为是「TLS False Start」,跟「TCP Fast Open」有点像,都是在还没连接完全建立前,就发送了应用数据,这样便提高了传输的效率。

接下来,分析每一个 ECDHE 握手过程。

a. TLS 第 1 次握手

客户端首先会发一个「Client Hello」消息,消息里面有客户端使用的 TLS 版本号、支持的密码套件列表,以及生成的随机数(Client Random)

b. TLS 第 2 次握手

服务端收到客户端的「打招呼」,同样也要回礼,会返回「Server Hello」消息,消息面有服务器确认的 TLS 版本号,也给出了一个随机数(Server Random),然后从客户端的密码套件列表选择了一个合适的密码套件。

不过,这次选择的密码套件就和 RSA 不一样了,我们来分析一下这次的密码套件的意思。

「 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384」

  • 密钥协商算法使用 ECDHE;
  • 签名算法使用 RSA;
  • 握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM;
  • 摘要算法使用 SHA384;

接着,服务端为了证明自己的身份,发送「Certificate」消息,会把证书也发给客户端。

这一步就和 RSA 握手过程有很大的区别了,因为服务端选择了 ECDHE 密钥协商算法,所以会在发送完证书后,发送「Server Key Exchange」消息。

这个过程服务器做了 3 件事:

  1. 选择了名为 x25519 的椭圆曲线,选好了椭圆曲线相当于椭圆曲线基点 G 也定好了,这些都会公开给客户端;
  2. 生成随机数作为服务端椭圆曲线的私钥,保留到本地;
  3. 根据基点 G 和私钥计算出服务端的椭圆曲线公钥,这个会公开给客户端。

为了保证这个椭圆曲线的公钥不被第三方篡改,服务端会用 RSA 签名算法给服务端的椭圆曲线公钥做个签名。

随后,就是「Server Hello Done」消息,服务端跟客户端表明:“这些就是我提供的信息,打招呼完毕”。

至此,TLS 两次握手就已经完成了,目前客户端和服务端通过明文共享了这几个信息:Client Random、Server Random 、使用的椭圆曲线、椭圆曲线基点 G、服务端椭圆曲线的公钥,这几个信息很重要,是后续生成会话密钥的材料。

c. TLS 第 3 次握手

客户端收到了服务端的证书后,自然要校验证书是否合法,如果证书合法,那么服务端到身份就是没问题的。校验证书的过程会走证书链逐级验证,确认证书的真实性,再用证书的公钥验证签名,这样就能确认服务端的身份了,确认无误后,就可以继续往下走。

客户端会生成一个随机数作为客户端椭圆曲线的私钥,然后再根据服务端前面给的信息,生成客户端的椭圆曲线公钥,然后用「Client Key Exchange」消息发给服务端。

至此,双方都有对方的椭圆曲线公钥、自己的椭圆曲线私钥、椭圆曲线基点 G。于是,双方都就计算出点(x,y),其中 x 坐标值双方都是一样的,前面说 ECDHE 算法时候,说 x 是会话密钥,但实际应用中,x 还不是最终的会话密钥

还记得 TLS 握手阶段,客户端和服务端都会生成了一个随机数传递给对方吗?

最终的会话密钥,就是用「客户端随机数 + 服务端随机数 + x(ECDHE 算法算出的共享密钥) 」三个材料生成的

之所以这么麻烦,是因为 TLS 设计者不信任客户端或服务器「伪随机数」的可靠性,为了保证真正的完全随机,把三个不可靠的随机数混合起来,那么「随机」的程度就非常高了,足够让黑客计算不出最终的会话密钥,安全性更高。

算好会话密钥后,客户端会发一个「Change Cipher Spec」消息,告诉服务端后续改用对称算法加密通信。

接着,客户端会发「Encrypted Handshake Message」消息,把之前发送的数据做一个摘要,再用对称密钥加密一下,让服务端做个验证,验证下本次生成的对称密钥是否可以正常使用。

d. TLS 第 4 次握手

最后,服务端也会有一个同样的操作,发「Change Cipher Spec」和「Encrypted Handshake Message」消息,如果双方都验证加密和解密没问题,那么握手正式完成。于是,就可以正常收发加密的 HTTP 请求和响应了。

3.6 小结

RSA 和 ECDHE 握手过程的区别:

  • RSA 密钥协商算法「不支持」前向保密,ECDHE 密钥协商算法「支持」前向保密;
  • 使用了 RSA 密钥协商算法,TLS 完成四次握手后,才能进行应用数据传输,而对于 ECDHE 算法,客户端可以不用等服务端的最后一次 TLS 握手,就可以提前发出加密的 HTTP 数据,节省了一个消息的往返时间;
  • 使用 ECDHE, 在 TLS 第 2 次握手中,会出现服务器端发出的「Server Key Exchange」消息,而 RSA 握手过程没有该消息。

我抓包:

4. HTTPS 如何优化?

由裸数据传输的 HTTP 协议转成加密数据传输的 HTTPS 协议,给应用数据套了个「保护伞」,提高安全性的同时也带来了性能消耗。

因为 HTTPS 相比 HTTP 协议多一个 TLS 协议握手过程,目的是为了通过非对称加密握手协商或者交换出对称加密密钥,这个过程最长可以花费掉 2 RTT,接着后续传输的应用数据都得使用对称加密密钥来加密/解密。

为了数据的安全性,我们不得不使用 HTTPS 协议,至今大部分网址都已从 HTTP 迁移至 HTTPS 协议,因此针对 HTTPS 的优化是非常重要的。

4.1 分析性能损耗

既然要对 HTTPS 优化,那得清楚哪些步骤会产生性能消耗,再对症下药。

产生性能消耗的两个环节:

  • 第 1 个环节,TLS 协议握手过程;
  • 第 2 个环节,握手后的对称加密报文传输。

对于第二环节,现在主流的对称加密算法 AES、ChaCha20 性能都是不错的,而且一些 CPU 厂商还针对它们做了硬件级别的优化,因此这个环节的性能消耗可以说非常地小。

而第一个环节,TLS 协议握手过程不仅增加了网络延时(最长可以花费掉 2 RTT),而且握手过程中的一些步骤也会产生性能损耗,比如:

  • 对于 ECDHE 密钥协商算法,握手过程中会客户端和服务端都需要临时生成椭圆曲线公私钥;
  • 客户端验证证书时,会访问 CA 获取 CRL 或者 OCSP,目的是验证服务器的证书是否有被吊销;
  • 双方计算 Pre-Master,也就是对称加密密钥;

为了大家更清楚这些步骤在 TLS 协议握手的哪一个阶段,我画出了这幅图:

4.2 硬件优化

玩游戏时,如果我们怎么都战胜不了对方,那么有一个最有效、最快的方式来变强,那就是「充钱」,如果还是不行,那说明你充的钱还不够多。

对于计算机里也是一样,软件都是跑在物理硬件上,硬件越牛逼,软件跑的也越快,所以如果要优化 HTTPS 优化,最直接的方式就是花钱买性能参数更牛逼的硬件。但是花钱也要花对方向,HTTPS 协议是计算密集型,而不是 I/O 密集型,所以不能把钱花在网卡、硬盘等地方,应该花在 CPU 上。

一个好的 CPU,可以提高计算性能,因为 HTTPS 连接过程中就有大量需要计算密钥的过程,所以这样可以加速 TLS 握手过程。另外,如果可以,应该选择可以支持 AES-NI 特性的 CPU,因为这种款式的 CPU 能在指令级别优化了 AES 算法,这样便加速了数据的加解密传输过程。

如果你的服务器是 Linux 系统,那么你可以使用下面这行命令查看 CPU 是否支持 AES-NI 指令集:

如果我们的 CPU 支持 AES-NI 特性,那么对于对称加密的算法应该选择 AES 算法。否则可以选择 ChaCha20 对称加密算法,因为 ChaCha20 算法的运算指令相比 AES 算法会对 CPU 更友好一点。

4.3 软件优化

如果公司预算充足对于新的服务器是可以考虑购买更好的 CPU,但是对于已经在使用的服务器,硬件优化的方式可能就不太适合了,于是就要从软件的方向来优化了。

软件的优化方向可以分层两种,一个是软件升级,一个是协议优化

先说第一个软件升级,软件升级就是将正在使用的软件升级到最新版本,因为最新版本不仅提供了最新的特性,也优化了以前软件的问题或性能。比如:

  • 将 Linux 内核从 2.x 升级到 4.x;
  • 将 OpenSSL 从 1.0.1 升级到 1.1.1;
  • ...

看似简单的软件升级,对于有成百上千服务器的公司来说,软件升级也跟硬件升级同样是一个棘手的问题,因为要实行软件升级,会花费时间和人力,同时也存在一定的风险,也可能会影响正常的线上服务。

既然如此,我们把目光放到协议优化,也就是在现有的环节下,通过较小的改动,来进行优化。

4.4 协议优化

协议的优化就是对「密钥交换过程」进行优化。

a. 密钥交换算法优化

TLS 1.2 版本如果使用的是 RSA 密钥交换算法,那么需要 4 次握手,也就是要花费 2 RTT,才可以进行应用数据的传输,而且 RSA 密钥交换算法不具备前向安全性。

总之使用 RSA 密钥交换算法的 TLS 握手过程,不仅慢,而且安全性也不高。因此如果可以,尽量选用 ECDHE 密钥交换算法替换 RSA 算法,因为该算法由于支持「False Start」,它是“抢跑”的意思,客户端可以在 TLS 协议的第 3 次握手后,第 4 次握手前,发送加密的应用数据,以此将 TLS 握手的消息往返由 2 RTT 减少到 1 RTT,而且安全性也高,具备前向安全性

ECDHE 算法是基于椭圆曲线实现的,不同的椭圆曲线性能也不同,应该尽量选择 x25519 曲线,该曲线是目前最快的椭圆曲线。

比如在 Nginx 上,可以使用 ssl_ecdh_curve 指令配置想使用的椭圆曲线,把优先使用的放在前面:

对于对称加密算法方面,如果对安全性不是特别高的要求,可以选用 AES_128_GCM,它比 AES_256_GCM 快一些,因为密钥的长度短一些。

比如在 Nginx 上,可以使用 ssl_ciphers 指令配置想使用的非对称加密算法和对称加密算法,也就是密钥套件,而且把性能最快最安全的算法放在最前面:

b. TLS 升级

当然,如果可以,直接把 TLS 1.2 升级成 TLS 1.3,TLS 1.3 大幅度简化了握手的步骤,完成 TLS 握手只要 1 RTT,而且安全性更高。

在 TLS 1.2 的握手中,一般是需要 4 次握手,先要通过 Client Hello (第 1 次握手)和 Server Hello(第 2 次握手) 消息协商出后续使用的加密算法,再互相交换公钥(第 3 和 第 4 次握手),然后计算出最终的会话密钥,下图的左边部分就是 TLS 1.2 的握手过程:

上图的右边部分就是 TLS 1.3 的握手过程,可以发现 TLS 1.3 把 Hello 和公钥交换这两个消息合并成了一个消息,于是这样就减少到只需 1 RTT 就能完成 TLS 握手

怎么合并的呢?

具体的做法是,客户端在 Client Hello 消息里带上了支持的椭圆曲线,以及这些椭圆曲线对应的公钥。

服务端收到后,选定一个椭圆曲线等参数,然后返回消息时,带上服务端这边的公钥。经过这 1 个 RTT,双方手上已经有生成会话密钥的材料了,于是客户端计算出会话密钥,就可以进行应用数据的加密传输了。

而且,TLS1.3 对密码套件进行“减肥”了, 对于密钥交换算法,废除了不支持前向安全性的 RSA 和 DH 算法,只支持 ECDHE 算法

对于对称加密和签名算法,只支持目前最安全的几个密码套件,比如 openssl 中仅支持下面 5 种密码套件:

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_GCM_SHA256
  • TLS_AES_128_CCM_8_SHA256
  • TLS_AES_128_CCM_SHA256

之所以 TLS1.3 仅支持这么少的密码套件,是因为 TLS1.2 由于支持各种古老且不安全的密码套件,中间人可以利用降级攻击,伪造客户端的 Client Hello 消息,替换客户端支持的密码套件为一些不安全的密码套件,使得服务器被迫使用这个密码套件进行 HTTPS 连接,从而破解密文。

4.5 证书优化

为了验证的服务器的身份,服务器会在 TLS 握手过程中,把自己的证书发给客户端,以此证明自己身份是可信的。

对于证书的优化,可以有两个方向:一个是证书传输,另一个是证书验证

a. 证书传输优化

要让证书更便于传输,那必然是减少证书的大小,这样可以节约带宽,也能减少客户端的运算量。所以,对于服务器的证书应该选择椭圆曲线(ECDSA)证书,而不是 RSA 证书,因为在相同安全强度下, ECC 密钥长度比 RSA 短的多

b. 证书验证优化

客户端在验证证书时,是个复杂的过程,会走证书链逐级验证,验证的过程不仅需要「用 CA 公钥解密证书」以及「用签名算法验证证书的完整性」,而且为了知道证书是否被 CA 吊销,客户端有时还会再去访问 CA, 下载 CRL 或者 OCSP 数据,以此确认证书的有效性。

这个访问过程是 HTTP 访问,因此又会产生一系列网络通信的开销,如 DNS 查询、建立连接、收发数据等。

CRL

CRL 称为证书吊销列表(Certificate Revocation List),这个列表是由 CA 定期更新,列表内容都是被撤销信任的证书序号,如果服务器的证书在此列表,就认为证书已经失效,不在的话,则认为证书是有效的。

但是 CRL 存在两个问题:

  1. 由于 CRL 列表是由 CA 维护的,定期更新,如果一个证书刚被吊销后,客户端在更新 CRL 之前还是会信任这个证书,实时性较差
  2. 随着吊销证书的增多,列表会越来越大,下载的速度就会越慢,下载完客户端还得遍历这么大的列表,那么就会导致客户端在校验证书这一环节的延时很大,进而拖慢了 HTTPS 连接。

OCSP

现在基本都是使用 OCSP ,名为在线证书状态协议(Online Certificate Status Protocol)来查询证书的有效性,它的工作方式是向 CA 发送查询请求,让 CA 返回证书的有效状态

不必像 CRL 方式客户端需要下载大大的列表,还要从列表查询,同时因为可以实时查询每一张证书的有效性,解决了 CRL 的实时性问题。

OCSP 需要向 CA 查询,因此也是要发生网络请求,而且还得看 CA 服务器的“脸色”,如果网络状态不好,或者 CA 服务器繁忙,也会导致客户端在校验证书这一环节的延时变大。

OCSP Stapling

于是为了解决这一个网络开销,就出现了 OCSP Stapling,其原理是:服务器向 CA 周期性地查询证书状态,获得一个带有时间戳和签名的响应结果并缓存它。

当有客户端发起连接请求时,服务器会把这个「响应结果」在 TLS 握手过程中发给客户端。由于有签名的存在,服务器无法篡改,因此客户端就能得知证书是否已被吊销了,这样客户端就不需要再去查询。

4.6 会话复用

TLS 握手的目的就是为了协商出会话密钥,也就是对称加密密钥,那我们如果我们把首次 TLS 握手协商的对称加密密钥缓存起来,待下次需要建立 HTTPS 连接时,直接「复用」这个密钥,不就减少 TLS 握手的性能损耗了吗?

这种方式就是会话复用TLS session resumption),会话复用分两种:

  • Session ID
  • Session Ticket

a. Session ID

Session ID 的工作原理是,客户端和服务器首次 TLS 握手连接后,双方会在内存缓存会话密钥,并用唯一的 Session ID 来标识,Session ID 和会话密钥相当于 key-value 的关系。

当客户端再次连接时,hello 消息里会带上 Session ID,服务器收到后就会从内存找,如果找到就直接用该会话密钥恢复会话状态,跳过其余的过程,只用一个消息往返就可以建立安全通信。当然为了安全性,内存中的会话密钥会定期失效。

但是它有两个缺点:

  • 服务器必须保持每一个客户端的会话密钥,随着客户端的增多,服务器的内存压力也会越大
  • 现在网站服务一般是由多台服务器通过负载均衡提供服务的,客户端再次连接不一定会命中上次访问过的服务器,于是还要走完整的 TLS 握手过程。

b. Session Ticket

为了解决 Session ID 的问题,就出现了 Session Ticket,服务器不再缓存每个客户端的会话密钥,而是把缓存的工作交给了客户端,类似于 HTTP 的 Cookie。

客户端与服务器首次建立连接时,服务器会加密「会话密钥」作为 Ticket 发给客户端,交给客户端缓存该 Ticket。

客户端再次连接服务器时,客户端会发送 Ticket,服务器解密后就可以获取上一次的会话密钥,然后验证有效期,如果没问题,就可以恢复会话了,开始加密通信。

对于集群服务器的话,要确保每台服务器加密 「会话密钥」的密钥是一致的,这样客户端携带 Ticket 访问任意一台服务器时,都能恢复会话。

Session ID 和 Session Ticket 都不具备前向安全性,因为一旦加密「会话密钥」的密钥被破解或者服务器泄漏「会话密钥」,前面劫持的通信密文都会被破解。

同时应对重放攻击也很困难,这里简单介绍下重放攻击工作的原理。

假设 Alice 想向 Bob 证明自己的身份。 Bob 要求 Alice 的密码作为身份证明,爱丽丝应尽全力提供(可能是在经过如哈希函数的转换之后)。与此同时,Eve 窃听了对话并保留了密码(或哈希)。交换结束后,Eve(冒充 Alice )连接到 Bob。当被要求提供身份证明时,Eve 发送从 Bob 接受的最后一个会话中读取的 Alice 的密码(或哈希),从而授予 Eve 访问权限。

重放攻击的危险之处在于,如果中间人截获了某个客户端的 Session ID 或 Session Ticket 以及 POST 报文,而一般 POST 请求会改变数据库的数据,中间人就可以利用此截获的报文,不断向服务器发送该报文,这样就会导致数据库的数据被中间人改变了,而客户是不知情的。

避免重放攻击的方式就是需要对会话密钥设定一个合理的过期时间

c. Pre-shared Key

前面的 Session ID 和 Session Ticket 方式都需要在 1 RTT 才能恢复会话。

而 TLS1.3 更为牛逼,对于重连 TLS1.3 只需要 0 RTT,原理和 Ticket 类似,只不过在重连时,客户端会把 Ticket 和 HTTP 请求一同发送给服务端,这种方式叫 Pre-shared Key

同样的,Pre-shared Key 也有重放攻击的危险。

如上图,假设中间人通过某种方式,截获了客户端使用会话重用技术的 POST 请求,通常 POST 请求是会改变数据库的数据,然后中间人就可以把截获的这个报文发送给服务器,服务器收到后,也认为是合法的,于是就恢复会话,致使数据库的数据又被更改,但是此时用户是不知情的。

所以,应对重放攻击可以给会话密钥设定一个合理的过期时间,以及只针对安全的 HTTP 请求如 GET/HEAD 使用会话重用。

4.7 小结

对于硬件优化的方向,因为 HTTPS 是属于计算密集型,应该选择计算力更强的 CPU,而且最好选择支持 AES-NI 特性的 CPU,这个特性可以在硬件级别优化 AES 对称加密算法,加快应用数据的加解密。

对于软件优化的方向,如果可以,把软件升级成较新的版本,比如将 Linux 内核 2.X 升级成 4.X,将 openssl 1.0.1 升级到 1.1.1,因为新版本的软件不仅会提供新的特性,而且还会修复老版本的问题。

对于协议优化的方向:

  • 密钥交换算法应该选择 ECDHE 算法,而不用 RSA 算法,因为 ECDHE 算法具备前向安全性,而且客户端可以在第三次握手之后,就发送加密应用数据,节省了 1 RTT。
  • 将 TLS1.2 升级 TLS1.3,因为 TLS1.3 的握手过程只需要 1 RTT,而且安全性更强。

对于证书优化的方向:

  • 服务器应该选用 ECDSA 证书,而非 RSA 证书,因为在相同安全级别下,ECC 的密钥长度比 RSA 短很多,这样可以提高证书传输的效率;
  • 服务器应该开启 OCSP Stapling 功能,由服务器预先获得 OCSP 的响应,并把响应结果缓存起来,这样 TLS 握手的时候就不用再访问 CA 服务器,减少了网络通信的开销,提高了证书验证的效率;

对于重连 HTTPS 时,我们可以使用一些技术让客户端和服务端使用上一次 HTTPS 连接使用的会话密钥,直接恢复会话,而不用再重新走完整的 TLS 握手过程。

常见的会话重用技术有 Session ID 和 Session Ticket,用了会话重用技术,当再次重连 HTTPS 时,只需要 1 RTT 就可以恢复会话。对于 TLS1.3 使用 Pre-shared Key 会话重用技术,只需要 0 RTT 就可以恢复会话。

这些会话重用技术虽然好用,但是存在一定的安全风险,它们不仅不具备前向安全,而且有重放攻击的风险,所以应当对会话密钥设定一个合理的过期时间。

摘自:https://xiaolincoding.com/

posted @ 2023-03-11 11:25  tree6x7  阅读(251)  评论(0编辑  收藏  举报