HTTP面试题 - HTTPS优化

2022-12-06 08:51:44 浏览数 (1)

引言

本节介绍HTTPS优化是一个不小的话题,关于优化的讨论是在其他软硬件合理配置的前提下进行的,而关于HTTPS,我们常常会想它肯定要比HTTP要慢,实际上一个优化良好的HTTPS有时候要比HTTP要快很多。

本节针对于TLS1.3以及TLS1.2的优化点存在一些差别,但是整体上更建议有条件就上TLS1.3,在整个TLS优化上的收益基本是最高的。

下面我们先分析一下相关的优化点。

PS:本文内容较长,不建议碎片化阅读,建议“阅读原文”。

分析优化点

我们根据TLS1.2的流程分析优化点,TLS1.2因为各种原因存在许多待优化的内容,从交互流程上面很容易看出一些计算消耗的问题:

  1. 证书校验;
  2. 密钥计算(主要是服务端改进);
  3. 计算会话密钥;
  4. 多次数据往返;
  5. 密钥交换算法的选择;

优化点

根据上面的简单讨论,我们整理出下面的主要优化点:

  • 2TT 对称密钥协商时间
  • 硬件优化
    • AES- NI的 CPU
    • SSL 加速卡
    • SSL 加速服务器
  • 软件升级
    • Linux升级
    • OpenSSL升级和漏洞修复
  • 协议优化
    • 尽量升级到TLS1.3
    • 使用 ECDHE 椭圆曲线函数,前向安全,速度快。
  • 会话重用
    • Session Id => TLS1.2 传统会话重用机制,但是对于服务端的负担很大。
    • Session Ticket => TLS 1.2 基于Session Id 一种改造升级,但是实际上有很大的安全隐患,Session Ticket的用来解密的会话密钥文件是固定的,推特利用了发牌器以及 tmfps 实现了会话密钥的定期轮换,以及会话密钥本身的有效期安全控制。(“Session Ticket”方案需要使用一个固定的密钥文件(ticket_key)来加密 Ticket,为了防止密钥被破解,保证“前向安全”,密钥文件需要定期轮换,比如设置为一小时或者一天)
    • 预共享密钥(PSK),PSK真正实现了0-RTT,通过“early data” key_share传输PSK,直接完成0-RTT握手,无需CA、CV、以及证书校验的过程。但是PSK的方式存在重放攻击的问题,不是很安全,所以实际使用的并不多。

硬件优化

硬件优化说白了就是充钱,只不过充钱要充对地方,需要注意HTTPS 协议是计算密集型,而不是 I/O 密集型,盯着硬盘和网卡升级是没有意义的,所以这时候使用自带AES的CPU的机器就显得十分重要了,支持 AES-NI 特性的 CPU可以在连接握手的时候利用CPU指令优化AES算法,这样也相当于加速了整个加密传输的过程。

除了CPU之外的其他方法是SSL加速卡和SSL加速服务器。“SSL 加速卡”作用是在加解密时调用它的 API,让专门的硬件来做非对称加解密,分担 CPU 的计算压力,但是加速卡存在缺陷,升级慢支持算法有限,不能灵活定制解决方案等问题。

于是这时候就要用到SSL服务器了,利用SSL服务器完全负担TLS协议加密和解密过程中的计算消耗,因为是独立服务器,也要比单纯的SSL加速卡要更加成熟和完善。但让部署SSL加速服务的硬件成本会显著上升。

软件升级

软件升级有时候能带来不错的收益,比如Linux 内核由 2.x 升级到 4.x,把 Nginx 由 1.6 升级到 1.16,把 OpenSSL 由 1.0.1 升级到 1.1.0/1.1.1。这些升级不仅可以解决一些被人熟知的漏洞,这些软件被成千上万的团队使用,经过各种考验和应用,本身十分成熟,升级也不会造成巨大的影响。

协议优化

现在大多数的支持HTTPS服务器基本都不会再用RSA算法了,因为RSA 密钥交换算法的 TLS 握手过程,不仅慢,而且安全性也不高,会出现前向安全性问题,同时还需要耗费2-RTT的往返时间,这个时间耗费也是TLS1.2的主要性能瓶颈。而以更为高效和前向安全椭圆曲线函数显然是更合适的选择,并且在TLS 1.3中把RSA相关等一系列不安全的算法给直接干掉了。

此外还需要注意ECDHE由于具备类似TCP Fast Open的功能,使用 ECDHE客户端可以不用等到服务器发回“Finished”确认握手完毕立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。换句话说就是ECDHE可以把握手时间降到1-RTT

ECDHE 算法是基于椭圆曲线实现的,说白了就是数学推导,所以必然存在算法和效率问题。不同的椭圆曲线性能不同,在选择函数曲线应该尽量选择 x25519 曲线,该曲线是目前最快的也是现今公认椭圆曲线。

在Nginx 上可以使用 ssl_ecdh_curve 指令配置想要使用的椭圆曲线,把优先使用的放在前面,但是需要注意要使用这个指定方式需要对于Nginx 进行升级,很多时候软件优化和协议优化有时候是捆绑到一起的。

具体可以看Ngnix文档:

https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ecdh_curve

通常情况下建议使用 ssl_ecdh_curve: X25519:secp384r1 的配置。但是根据一个国外的统计发现,全球四分之三的互联网用户更喜欢 P-256 而不是其他曲线,这可能是仅仅是因为它是 OpenSSL 的默认曲线。

具体可以看这一篇文章,下面是根据 2018年统计结果图,到目前来看依然占不小比重:

# Everyone Loves Curves! But Which Elliptic Curve is the Most Popular?

如何选择一个安全的椭圆曲线,这里两篇讨论,第一篇对于当时市面流行的加密曲线函数进行横比,比较了算法之间的效率(注意第一个文章的False不是说不安全,而是说安全实现起来要网站认为的安全函数要复杂一些,本身实际上是保证安全的)。

https://safecurves.cr.yp.to/https://security.stackexchange.com/questions/78621/which-elliptic-curve-should-i-use

下面是相关语法:

代码语言:javascript复制
Syntax:

ssl_ecdh_curve _curve_; 

Default:

ssl_ecdh_curve auto;

Context:

http, server

This directive appeared in versions 1.1.0 and 1.0.6.

Tip:该指令出现在版本 1.1.0 和 1.0.6 中。作用是为 ECDHE 密码指定椭圆函数曲线的公式。

此外使用 OpenSSL 1.0.2 或更高版本时可以指定多条曲线 (1.11.0),例如:

ssl_ecdh_curve prime256v1 : secp384r1;

Tip:为了方便阅读,中间加了空格,实际配置冒号两边是没有空格的。

特殊值auto(1.11.0),表示 nginx 在使用 OpenSSL 1.0.2 或更高版本或 默认曲线为prime256v1 旧版本时,使用内置于 OpenSSL 库中的列表。

在 1.11.0 版本之前不进行手动置顶的情况下,prime256v1为默认使用曲线。

当使用 OpenSSL 1.0.2 或更高版本时,该指令设置服务器支持的曲线列表。因此,为了使 ECDSA 证书发挥作用,更为重要的是使用包含在证书中的曲线。

TLS 升级

前面我们简单涉及到了一点点TLS1.3的升级好处,这里再进行更深入的讨论。

首先,TLS1.3 最为直观的升级是把四次握手升级为三次握手,也就是把 2-RTT的握手交流转为1-RTT就可以完成,当然这里你可能会说 ECDHE 不是也能做到1-RTT么 ?你猜的没错,TLS1.3实际上就是把ECDHE标准化了,借用椭圆函数曲线搭了一波便车提速。

而关于TLS1.3的其他升级,这里简单列举几个点,更多内容请阅读【HTTP】TLS1.3 初次解读

  1. TLS1.3 把Key Exchange 的步骤合并到Client Hello 中进行交互,减少1RTT握手时间。
  2. 废除了大量不安全的算法, 最终仅剩下5个被公认安全和有保障的加密套件。
  3. 完全废弃了非对称加密的密钥交换方式,转而使用具备前向安全并且目前公认无已知手段破解的ECDHE 算法。
  4. 通过Pre-share-Key 和early_data 实现了0-RTT的会话重用,十分诱人的升级,但是代价是牺牲对于重放攻击的防护,但是是可以被服务端通过编码等各种手段解决的。不过这种存在隐患的方案最终用的比较少。

证书优化

在TLS1.2的交互流程,握手过程中的证书验证也是一个比较耗时的操作,每次都需要通过传输证书链的方式进行校验,然后还需要通过客户端再逐一校验一遍。所以可以发现这里证书方面有两个优化点,第一个点是传输证书,第二个点是证书校验

传输证书优化

传输证书优化可以从证书本身入手,现在的服务器基本都使用 ECDHE 椭圆曲线函数进行密钥交换,传输证书自然也建议使用此算法。

比对RSA和ECDHE算法效率,这里可以举个简单的例子,2048位的RSA和224位的ECDHE的椭圆曲线函数是一样的,这意味着ECDHE本身只需要传输更少的位数就可以实现安全传输,同时因为占用更少的带宽就可以传输证书,所以效率自然也就上来了。

证书校验优化

TLS1.2的文章中介绍了有关证书链检验,证书校验简单来说就是从上至下的“链式调用”的过程,从根证书到服务器证书层层校验,通过这一套自证明体系完成证书的合法性校验。

需要注意在这些内容中还隐藏了很多细节,因为证书不是永久生效的,所以还存在额外的证书有效期校验的过程

因为证书的有效期需要访问CA,所以这里可能又存在网络开销,比如访问CA的网址,下载 CRL 或者 OCSP 数据,而这个过程因为需要获取IP,所以 DNS 查询、建立连接、收发数据等一系列网络通信必不可少,这又平白无故增加好几个 RTT,但是操作又是必要的。

在过去很长一段时间,HTTPS就是在这样的证书校验体系下工作,所以CA进行改良,改为使用 CRL(Certificate revocation list,证书吊销列表)完成证书校验工作。

CRL由 CA 定期发布,里面包含所有被撤销信任的证书序号,查询这个列表就可以知道证书是否有效。

但是这个列表存在两个显著问题:

  1. 实时性:因为是定期更新,所以客户端有可能拿到过期的服务端证书。
  2. 因为吊销证书是“增量更新”,所以随着时间推移,吊销证书列表会存在明显的更新缓慢和占用大量资源的问题,浪费时间去下一个上 MB的列表才能连接上网显然十分不合理。

所以现在基本都不会走CRL的方式,而是使用OCSP(在线证书状态协议,Online Certificate Status Protocol) 方式校验,主要思路是向 CA 发送查询请求,让 CA 返回证书的有效状态,等于又回去了,得,改了一遍又改回去了。

CRL是由于过去网络带宽资源有限,所以这样的设计合理,但是随着互联网飞速发展和4G以及无线移动网和WI-FI普及,这样的烦恼已经是过去式。

但是因为 OCSP 说白了又是回到过去,也要多出一次网络请求的消耗,而且还依赖于 CA 服务器本身性能是否能及时响应,如果 CA 服务器繁忙的阶段并且如果还是校验跨地域的证书,那响应延迟是等不起的。

为了解决这个问题,OCSP后续也进行了升级,叫“OCSP Stapling”(OCSP 装订),说白了就是把CA校验的过程“前置处理”,在进行HTTPS握手之前,CA就已经把证书的最新状态返回给服务器,服务器就可以随着握手跟着证书一起发出去,同时也可以免除CA服务器的查询时间。

所以整个过程变化就是:走CA->走过期列表->走CA,但是提前走,这样的模式。

会话复用

会话复用指的是在HTTPS连接的过程中每次都需要提供4个参数才能算出对称加密的密钥,整个计算过程不仅需要多次数据往返,还需要耗费大量计算机资源算出密钥Master Secret,每次都计算主密钥显然太浪费时间了,于是自然会想到把这个会话密钥复用一下。

Client RandomServer RandomClient ParamsServer Params,加上 HKDF 算法算出对称加密的密钥。

会话复用在TLS1.2和TLS1.3是不同的,这里先介绍TLS1.2的会话复用,再介绍TLS1.3 的改进。

TLS1.2的会话复用存在两个主要用法,第一种是我们耳熟能详的SessionId,第二种是稍微复杂一些的Session Ticket,也叫做“会话票证”,但是TLS1.2 Session Ticket问题一大把,设计本身也不合理,要安全使用需要借助其他的兜底方案,所以TLS1.3对于Session Ticket改进,最终提出了PSK作为最终的会话密钥计算方案,并且借助一些辅助数据实现0-RTT......下面我们按照顺序进行解答。

Session ID

Session Id的特点是简单实现方便,易用性十分强,并且不算是非常复杂,而缺点是如果网站的访问用户量非常大,存储Session Id 对于整个服务器的内存是一个非常的大的挑战,同时面对跨越多个服务器的SessionId同步也是一个问题,所以早期的解决方案是使用Redis或者其他方案持久化存储。

Session ID 是一个Key/Value 的关系,通常在Client Hello 这一步进行传输, 双方在完成连接之后会通过唯一的Session ID 标识。

而重用是指在第一次HTTPS连接之后,下一次访问客户端就会携带Session ID ,然后服务端会通过内存寻找这个会话ID是否使用过,如果找到则会跳过身份检测动作并且重用会话。

Session ID的重用会话是 1-RTT的,还需要一次报文验证的往返以及新会话密钥的重新生成,保证前向安全。

Session Id 通常会传输下面的参数:

  • 会话标识符(session identifier): 每个会话的唯一标识符
  • 对端的证书(peer certificate): 对端的证书,一般为空
  • 压缩算法(compression method): 通常禁用。
  • 密码套件(cipher spec): Client 和 Server 协商共同协商出来的密码套件
  • 主密钥(master secret): 注意这里不是预加密的密钥,而是直接生成的主密钥,因为预备密钥是为了算出主密钥服务的,临时的密钥不具备安全性,而利用临时密钥加双向随机参数计算的结果最为可靠,这个是无法反向破解的。

为什么使用主密钥?Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样,即使一个会话的主密钥泄露了或者被破解了也不会影响到另一个会话。其实就是对于前向安全性的保护。

  • 会话可恢复标识(is resumable): 标识会话是否可恢复。

下面是使用Session Id 的恢复会话流程:

客户端发送服务端之前返回的Session Id,此时Server 的内存中会保存一份 Session Cache 的字典,key 是 Session ID,value 是会话信息,根据传过来的 Session ID 查看是否有相关的会话信息,如果存在就可以恢复会话,就可以直接发送直接发送 ChangeCipherSpec 和 Finished 消息。

代码语言:javascript复制
      Client                                                Server

      ClientHello                   -------->
                                                       ServerHello
                                                [ChangeCipherSpec]
                                    <--------             Finished
      [ChangeCipherSpec]
      Finished                      -------->
      Application Data              <------->     Application Data

使用会话密钥的好处

  • 2-RTT 到 1-RTT 握手升级。
  • 减少因为加密算法等参数计算的CPU消耗。

使用会话密钥的坏处

  1. Server 会话信息限制了扩展能力。
  2. 分布式系统如果简单存储Session Cache需要做共享数据同步。

注意Nginx 官方并没有提供支持分布式服务器的 Session Cache 的实现,所以需要第三方组件提供存储支持,实现的方案也很多,比较简单的是使用第三方缓存中间件。

Session Id 是早期互联网的常用会话重用方案,但是在业务和访问量膨胀之后往往需要大量的兜底方案支撑Session Id实现多服务同步,所以TLS后续出现了 Session Ticket的方式。

Session Ticket

Session Ticket 本身类似 Http cookie 的原理,主要的思想也是把所有的状态信息放在客户端,服务端在第一次建立连接之后,根据自己内部的会话密钥对于会话参数加密,官方把这个加密的结果被叫做 票证,服务端完成握手就可以把票证通过会话传给客户端。

理解:和我们去火车站提前买票坐车的道理有点类似。

会话i密钥获取过程

  • 客户端发送支持的参数;
  • 服务器选择参数,并将证书与 (EC)DHE 密钥交换的前半部分一起发送;
  • 客户端发送 (EC)DHE 交换的后半部分,计算会话密钥并切换到加密通信;
  • 服务器计算会话密钥并切换到加密通信。

Session Ticket

使用(EC)DHE的算法是确保了前向安全性,

会话票证可减少握手的开销。当客户端支持会话票证时,服务器将使用只有服务器拥有的密钥(会话票证加密密钥 (STEK))加密会话密钥,并将其发送到客户端。客户端收到之后需要保存好会话票证以及如何还原会话密钥的必要参数。Session Ticket特点是即使服务端忘记客户端的密钥,也可以进行无状态部署,因为会话密钥加密依赖外部的会话密钥加密文件。

STEK:解密票证,提取会话密钥d的时候会被使用

所谓无状态部署是指服务端传输完会话票证之后就不需要再关心票证存储,只要服务器持有会话票证加密密钥 (STEK)就可以进行解密验证,这是和Session Id 最大的不同,服务端不再负担缓存存储压力,减轻了服务器的存储负担。

会话恢复过程

客户端收到票证之后,下一次请求在ClientHello 的扩展字段 session_ticket 中携带加密信息将票证提交回服务器,服务端校验通过则恢复会话。

注意用票证的验证方式和 Session Id 塞入主密钥的做法有点不太一样。Session Ticket的方式解密Ticket就可以直接获得主密钥,但是在客户端存储的时候实际上却只返回和存储预备主密钥,在和服务端进行会话重用,并且简单握手之后,才会用预备主密钥算出 主密钥 的方式进行验证。

不直接存储主密钥的做法是考虑到服务端集群部署需要存储以及同步Session Cache、Session ticket两个变量同步,而预备主密钥的存储方式可以适用于集群部署的服务端,双方只要同时支持Session Ticket 交互,就可以通过票证在扩展字段中快速传输比对。如果不支持则回退到完整握手,也不会有太大影响。

看到这里相信读者肯定会有疑问,为什么Session Id 存主密钥,而 Session Ticket却存预备密钥?

这是因为Session Ticket服务端和客户端需要先进行一次简单验证,然后客户端需要根据预备密钥计算出主密钥,之后再计算出会话密钥,服务端只需要把票证解密,比对两边的会话密钥是否一致即可。

所有的细节都是在客户端这边完成,服务端不需要做任何事情,它唯一需要做的是把拿到的票证解密然后比对完整性,解密内容,对的上说明可以恢复会话。

完成会话恢复之后,接着客户端和服务端还需要根据当前连接使用的新的”Server Random“、"Client Random",和预备密钥等参数,再用PRF函数计算出新的会话密钥,这是处于会话密钥的前向安全性考虑,确保即使本次请求被破解也无法解除其他抓包请求。

这一块可能比较绕,不是很好理解,这里举一个不恰当的例子。

角色:老师和学生。过程:老师为了鼓励学生学习,他想到一个绝招,那就是把写好了答案的非常难的卷子(会话密钥加密前的会话信息)发给同学们限时一段时间内看一遍(会话),看完了用然后咒语改写这张卷子(会话密钥加密之后的加密信息)。接着把改写过内容的卷子以及暗号(预备加密密钥)告诉学生们,老师我持有解密的咒语(STEK)能知道所有题目和答案,他要考验你们自己想办法如何利用暗号把卷子的内容和答案还原出来,每个人的暗号都是独一份(会话密钥唯一性),你们之间没法互相抄,也没法互相模仿。

准备妥当之后,此时老师接着和学生说你们之后3天内只要用暗号把题目和答案写出来,并且和我的答案一模一样的,期末考试直接满分,逾期的不给这个福利。

学生们绞尽脑汁的根据暗号回忆出看到的内容,大部分同学都回忆出来的,而老师后面因为以太忙翻脸不认账,只是说”我不记得题目了,也不知道答案,我只记得解密咒语(STEK)能解开你们的那份内容,那这样,如果你们给我加密文件和(会话密钥加密之后的加密信息)你们的答案(预加密密钥算出来的主密钥),我解密出来的内容如何和你们给的一模一样,我就承认并且答应给你们直接满分。

实际上,老师内心窃喜,自己只要用咒语解密卷子对答案就可以,其他啥都不需要干,还不用改卷子和保管卷子,学生们还能积极的答题,真是太棒了!

补充:写好了答案的卷子通常会有下面这几种情况:

  1. 写满答案的卷子出现残缺,有部分内容字迹不清。
  2. 写满答案的卷则出现了两边不一样的题目和不一样的答案。
  3. 交白卷,你是在骗傻子嘛?
  4. 逾期作废。

以上情况,老师的答案都是继续努力。(还原完整会话) ....故事完

根据上面的例子和描述,我们最后看看Session Ticket的传输过程。

总结整个传输流程图,就是类似下面的情况:

客户端获取到会话票证之后,下次客户端想要连接到该服务器时,它会将票证与初始参数算出来的主密钥一起发送。如果服务器仍然具有对应的STEK,它将解密票证,提取会话密钥比对校验,校验通过就开始使用会话连接就此完成交互,并通过跳过密钥协商以及CA校验来节省往返行程。如果会话协商比对失败或者其他异常,则客户端和服务器将回退到正常握手。

但是 Session Ticket 最大的问题是 STEK 的会话密钥是固定的,并以此延伸出其他几个致命问题。

Session Ticket 致命问题

问题1:无前向安全性

我们把整个交互流程多读几遍就会发现问题,那就是会话密钥的存储也是类似RSA的非对称密钥加密的方式,以及和RSA一样的前向安全问题,服务端的密钥是不需要变动的,服务器存储Session Ticket不进行定期刷新就是永久有效的,而只在客户端的会话票证存在有效期。

所以只要攻击者通过漏洞入侵服务器,就可以通过服务器的 STEK 对于其他所有的会话请求进行破解,SSL的安全性也就不复存在了。并且攻击者定期重用会话,那么STEK的有效期会进一步延长,神不知鬼不觉的偷更多未来的数据,这显然是非常危险的。

问题2:STEK 共享隐患

STEK是基于无状态服务进行部署,虽然服务端不需要关心Session cache,但是服务端是集群部署那么就需要共享STEK,而在共享STEK的过程中就会存在会因为网络传输产生很多安全隐患。

这不是又回到Session Id 的模式了?不过维护的东西少了很多(也算进步吧),但是本质没有变 =-=。

(╯°□°)╯︵ ┻━┻

可恶!

TLS 1.3 通过有效地对当前密钥进行哈希处理(单向函数)来解决此问题,这一点会在下文说明这样做的理由。遗憾的是在TLS1.2中并没有结构完整的会话密钥加密来完成类似处理,所以它需要服务端提供者自己想办法处理。

下面解释推特对于 Session Ticket 致命问题的解决方案,如果想了解推特处理更多其他解释说明和细节,可以看推特官方博客的考古文章:推特上的前向保密 (twitter.com)

推特 在2013年给出了TLS 1.2 Session ticket 会话密钥文件永久存储问题的解决方案,思路是使用一个发牌器,因为推特访问量非常大,发牌器之间需要组成集群确保稳定,选举其中一台成为master。master每 12 小时生成一个新的会话票证密钥,并在 36 小时后将旧密钥归零。密钥存储在 tmpfs(基于 RAM 的文件系统)中,并且不配置交换分区

这种处理的重点是想办法不要进行会话密钥的分发交换任何中间媒介的交换存储,使用统一存储tmpfs替代,最大的缺点可能会将密钥写入长期磁盘存储,所以需要定期清理。当然除开 tmpfs 的方案,也可以使用redis等中间件构建集群存储,思路是差不多的。总之,想办法让服务器集群的会话密钥存储在一个地方最为稳妥。

上面处理完会话密钥的安全性之后,接着是前端通过发牌器获取最新的票证密钥,每5分钟收到刷新票证请求,这里注意这个通知本身也是使用前向安全的DHE算法加密保证安全传输(这是Session Ticket的另一个致命问题),确保不会泄露自身的任何信息给攻击者作为参考依据。

对于一个客户端的会话密钥,推特的设置是至少20分钟有效期,如果中间没有被刷新掉就可以认为是最新的。当然这都是2013年的设置,现在的处理方案各方面都已经不一样了,因为推特早就已经支持 TLS1.3 。

讨论这些看似无用的内容,是让我们从另一个方面说明一个存在缺陷的标准或者方案,往往需要更多可能变得复杂的架构兜底来确保安全,然而TLS1.3仅仅加了一次哈希就解决了这个问题。

问题3:票证明文传输

第三点也是最致命的一点,实在是想不到为什么设计Session Ticket的人要在 TLS1.2 握手的 ChangeCipherSpec 之前让客户端把票证以明文的方式传输,个人的猜想是可能想要实现类似TLS1.3 0-RTT连接的效果。(实际上0-RTT的代价不小)

因为TLS1.2的Session Ticket是明文传输完全依赖 STEK 解密验证的,这完全绕过了信息保密这一步,所以也给了黑客替换伪装客户端的可乘之机。

TLS1.3 解决问题方案也是显而易见,那就是会话的密钥也要加密之后再进行传输验证,而这一层加密就是对密钥做一次哈希,这样确保了传输会话密钥的过程安全,在Client Hello 中携带函数的相关信息,在服务端收到之后就可以进行解密。

问题4:无CA

多看几遍session ticket的传输会发现会话连接直接跳过CA校验,显然是非常危险的。

合体:无安全性

这几个缺陷是1 1 1 1>N,后患无穷,最终的效果是攻击者可以盗取会话加密密钥文件被动解密支持会话票证的所有连接,不管客户端有没有重用会话连接,攻击者可以利用抓取的请求获取加密传输信息,这和RSA的问题有着异曲同工之妙,甚至更为严重。

Server 不支持 SessionTicket 处理

如果服务端不支持SessionTicket,RFC规定如果服务端返回的SessionTicket扩展字段中没有内容(为空),则客户端可以认为服务端不支持SessionTicket,需要回退到正常握手流程。

具体可以看下面的流程:

代码语言:javascript复制
Client                                               Server

         ClientHello
         (SessionTicket extension)    -------->
                                                         ServerHello
                                                        Certificate*
                                                  ServerKeyExchange*
                                                 CertificateRequest*
                                      <--------      ServerHelloDone
         Certificate*
         ClientKeyExchange
         CertificateVerify*
         [ChangeCipherSpec]
         Finished                     -------->
                                                  [ChangeCipherSpec]
                                      <--------             Finished
         Application Data             <------->     Application Data

Server 校验 SessionTicket 失败

如果Server建立的票证但是握手过程中失败,那么客户端需要删除无效票证,同样如果服务端收到票证但是校验票证失败,也需要回退到完整握手。

具体的交互流程依然和上面类似。

代码语言:javascript复制
         Client                                               Server

         ClientHello
         (SessionTicket extension) -------->
                                                         ServerHello
                                     (empty SessionTicket extension)
                                                        Certificate*
                                                  ServerKeyExchange*
                                                 CertificateRequest*
                                  <--------          ServerHelloDone
         Certificate*
         ClientKeyExchange
         CertificateVerify*
         [ChangeCipherSpec]
         Finished                 -------->
                                                    NewSessionTicket
                                                  [ChangeCipherSpec]
                                  <--------                 Finished
         Application Data         <------->         Application Data

PSK 和 New Session Ticket

因为Session Ticket的种种问题,TLS.1.3直接废除了原有的 Session ID 和 Session TIcket 的方式,TLS1.3 提出了 NST(New Session Ticket) PSK 观念。

NST主要用于参与PSK密钥的计算,双方通常会在进行 application_key(主密钥) 密钥协商计算完成之后,计算出 resumption_key(恢复会话主键),然后使用 resumption_key 与 PSK初始值做HKDF计算才会得到真正的PSK值,用于下一次TLS连接的建立。

PSK是TLS1.3出现的新术语,叫新密钥交换身份认证机制,TLS1.3的NST(New Session Ticket)和TLS1.2的NST(New Session Ticket)是一样的,PSK首次握手之后由服务端生成票证并且发给客户端。

在TLS1.3中,客户端在下一次请求中携带PSK,发送 Ticket 的同时会带上应用数据(Early Data),并且通过Client Hello 中进行传输。当服务端通过客户端请求的 key_share 中的找到PSK,解密验证密钥安全的情况下,可以忽略掉CA身份认证和密钥交换算法协商以及CV的步骤,这时不需要一次数据往返,发送过去就可以直接是密文,然后由服务端进行解密,直接完成请求的加密传输。

但是如果PSK校验失败,那么服务器还要从借用key_share和客户端重新进行完整的协商过程。

以上就是PSK会话协商的大致过程,0-RTT的吸引力很强,但是是以“弱”安全性的重放攻击换来更高的效率,服务端如果想要享受0-RTT的效率就必须要对于重放攻击做出相关的安全防护。

也是因为0-RTT 的副作用,所以PSK的密钥交换方式实际在HTTPS上的使用并不多。

重放攻击

重放攻击的步骤可以参考下图:

PSK密钥交换过程

PSK 的对比TLS1.2的Session Ticket的交互流程变化可以看下面的图:

总结

这一节从HTTPS的性能分析,讲述了TLS的优化点以及各个性能优化点,个人拓展讲述了会话复用的细节。因为相对比较复杂所以放到了最后解释。

写在最后

有关HTTPS的内容个人已经告一段落了,有关TLS1.2和TLS1.3的详情内容,可以参考下面这两篇文章:

【HTTP】HTTPS TLS 1.2

【HTTP】TLS1.3 初次解读

0 人点赞