在未经任何优化的情况下,HTTPS 会严重降低用户的访问速度。主要因素包括:
网络耗时。由于协议的规定,必须要进行的网络传输。比如 SSL 完全握手,302 跳转等。最坏情况下可能要增加 7 个 RTT。计算耗时。无论是客户端还是服务端,都需要进行对称加解密,协议解析,私钥计算,证书校验等计算,增加大量的计算时间
如下图所示:
HTTPS 请求如图所示:(有的访问可能还会多一个步骤②)
相对于 HTTP 来说,很可能会额外增加②到⑧的开销。而其中完全握手阶段的性能消耗占 HTTPS 整体性能消耗的 90%以上,所以这块也是需要重点优化的地方,我们使用的是 CDN/STGW 提供的优化方案。
会话复用策略通过对已经建立 TLS 会话的合理复用,不需要进行非对称密钥交换计算减少了 CPU 消耗,同时不需要进行完全握手阶段二⑧,节省一个 RTT 和计算耗时,可大幅提高服务器的 TLS 性能。
CDN/STGW 对于 session ID 和 session ticket 两种会话复用机制均支持,默认使用的是 session ticket。session ticket 占用服务器资源很少,支持多机间分布式缓存;但需要服务器和客户端都支持。
HTTPS 协议中最消耗 CPU 计算资源的就是非对称密钥交换的计算,通过对 Nginx 源码进行改造,将最消耗 CPU 的加解密计算过程剥离出来,避免在本地 CPU 上进行同步计算,而使用远程 SSL 硬件加速集群进行异步计算。整个过程是异步的,上层应用程序(Nginx)不需要等待 RSA 计算结果的返回就能接收其他请求。这也是 CDN/STGW 用于大规模 HTTPS 接入的 杀手锏 解决方案之一。
- HSTS、OCSP Stapling 等其他优化 ( 暂未实施 )
除了这些性能提升明显的核心优化,我们后续也将考虑 HSTS、OCSP Stapling 等优化。
HSTS 跳转:
基于快速回退考虑,我们目前是在服务器端做 302 跳转,跳转到 HTTPS。但这个 302 跳转存在两个问题:使用不安全的 HTTP 协议进行通信并且增加一个 Round-Trip Time。
而 HSTS 是 HTTP Strict Transport Security 的缩写,服务器端配置支持 HSTS 后,会在给浏览器返回的 HTTP Header 中携带 HSTS 字段,浏览器在获取到该信息后,在接下来的一段时间内,对该网站的所有 HTTP 访问,浏览器都将请求在内部做 307 跳转到 HTTPS,而无需任何网络过程。
OCSP Stapling:
在 HTTPS 通信过程时,浏览器会去验证服务器端下发的证书链是否已经被撤销。验证的方法有两种:CRL 和 OCSP。OCSP Stapling 是对 OCSP 缺陷的弥补,服务器可事先模拟浏览器对证书链进行验证,并将带有 CA 机构签名的 OCSP 响应保存到本地,然后在握手阶段,将 OCSP 响应和证书链一起下发给浏览器,省去浏览器的在线验证过程。
(2)减少 HTTPS 连接数量
除了使单个 HTTPS 连接变得更快,HTTPS 请求数量方面也是不错的优化点。
HTTP/2 是 IETF 基于 SPDY 开发的下一代 HTTP 协议。HTTPS 传输在增加 SSL 握手、加解密开销时延后,HTTP/2 采用了二进制分帧层、请求优先级、链路复用、服务器推送、首部压缩等技术来提升 HTTPS 的性能。
其中链路复用的方式能将多个请求在同一个连接上一起发出去,对 HTTPS 通信效率提升明显。链路复用配合域名收敛效果更加,理论上域名收敛越好,链路复用性能提升越明显。使用 HTTP/2 需要特别注意头部信息的变化,HTTP/2 的头部使用小写字母键值对的方式,和 HTTP/1.x 区别还是比较大。
我们目前接入 STGW 的 CGI 域名基本都开启了 HTTP/2,后续也会对 CDN 的静态业务开启 HTTP/2。CGI、静态资源有进行域名收敛,不过力度还不大,这也是我们后续性能优化的重点。
优化思路:
前文提到 HTTPS 非常慢,我们也主要从两个层面对访问速度进行了优化:协议栈和前后端资源。
全链路协议栈优化
HTTPS 可以认为是 HTTP over SSL,而 HTTPS 又是使用 TCP 协议进行传输,所以整个协议栈的优化涉及到三个层面:
TCP 优化。包括拥塞窗口的调整,tcp fast open,reuseport 的支持,最新的 BBR 拥塞控制算法的支持等。
SSL 协议优化。分布式 session cache, session ticket,False start, ocsp stapling file, 动态 record size 等。
SSL 协议优化最重要的目标还是提升简化握手的比例。腾讯云通过实现全局 session cache 和全局 session ticket 来提升 SSL 的简化握手比例,节省用户访问时间和计算资源。
应用层协议优化。同时支持 SPDY,HTTP2,HSTS 等。
HTTP2 相比 HTTP1.X 最大的优势就是多路复用,能够将多个 HTTP 请求通过一个 TCP 连接并行发送,相比 HTTP1.X 的串行发送,性能无疑是提升很多。
由于 HTTP2 是从 SPDY 继承发展出来的,所以部分较老的客户端只支持 SPDY,不支持 HTTP2。而大部分新客户端,只支持 HTTP2,不支持 SPDY。为了同时兼容新老客户端的性能,腾讯云同时支持 SPDY 和 HTTP2,最大化提升新老版本客户端的性能。
前后端优化
HTTP2 及 SPDY 最大的特性和优势就是多路复用,能够将多个请求通过一个连接并行发送出来。这个特性虽然很强大,但是如果还使用传统的 HTTP 优化策略,多路复用的效果会很有限。比如域名分片,pipline 等都会影响多路复用的效果。于是我们又通过多次的数据实验,调整了一些前端资源包括后端接入的策略:
域名收归。通过页面资源及性能分析,确实域名收归方案,比如移动页面不超过 3 个。
预建连接。STGW 提供预连接页面,通过对热点页面的用户行为进行分析,提前建立连接,减少协议开销对用户体验的影响。
1. 问题 GMssl能否也这样优化?
随着 HTTP/2 的逐渐普及,以及国内网络环境越来越糟糕(运营商劫持和篡改),HTTPS 已经开始成为主流。HTTPS 在 TCP 和 HTTP 之间增加了 TLS(Transport Layer Security,传输层安全),提供了内容加密、身份认证和数据完整性三大功能,同时也给 Web 性能优化带来新的挑战。上次写的「使用 BoringSSL 优化 HTTPS 加密算法选择」一文中,我介绍了如何针对不同平台启用最合适的传输加密算法。本篇文章我打算继续写 HTTPS 优化 —— TLS 握手优化。
TLS 的前身是 SSL(Secure Sockets Layer,安全套接字层),由网景公司开发,后来被 IETF 标准化并改名。通常没有特别说明时,SSL 和 TLS 指的是同一个协议,不做严格区分。
TLS 握手
在传输应用数据之前,客户端必须与服务端协商密钥、加密算法等信息,服务端还要把自己的证书发给客户端表明其身份,这些环节构成 TLS 握手过程,如下图所示:
可以看到,假设服务端和客户端之间单次传输耗时 28ms,那么客户端需要等到 168ms 之后才能开始发送 HTTP 请求报文,这还没把客户端和服务端处理时间算进去。光是 TLS 握手就需要消耗两个 RTT(Round-Trip Time,往返时间),这就是造成 HTTPS 更慢的主要原因。当然,HTTPS 要求数据加密传输,加解密相比 HTTP 也会带来额外的开销,不过对称加密本来就很快,加上硬件性能越来越好,所以这部分开销还好。
详细的 TLS 握手过程这里就不介绍了,大家可以通过这两篇文章去了解:
- 大型网站的 HTTPS 实践(一)—— HTTPS 协议和原理
- Keyless SSL: The Nitty Gritty Technical Details
通过 Wireshark 抓包可以清楚地看到完整 TLS 握手过程所需的两个 RTT,如下图:
False Start
False Start 有抢跑的意思,意味着不按规则行事。TLS False Start 是指客户端在发送 Change Cipher Spec Finished 同时发送应用数据(如 HTTP 请求),服务端在 TLS 握手完成时直接返回应用数据(如 HTTP 响应)。这样,应用数据的发送实际上并未等到握手全部完成,故谓之抢跑。这个过程如下图所示:
可以看到,启用 False Start 之后,TLS 阶段只需要一次 RTT 就可以开始传输应用数据。False Start 相当于客户端提前发送加密后的应用数据,不需要修改 TLS 协议,目前大部分浏览器默认都会启用,但也有一些前提条件:
- 服务端必须在 Server Hello 握手中通过 NPN(Next Protocol Negotiation,下一代协议协商,Google 在 SPDY 协议中开发的 TLS 扩展,用于握手阶段协商应用协议)或 ALPN(Application Layer Protocol Negotiation,应用层协议协商,NPN 的官方修订版)表明自己支持的 HTTP 协议,例如:http/1.1、http/2;
- 使用支持前向安全性(Forward Secrecy)的加密算法。False Start 在尚未完成握手时就发送了应用数据,Forward Secrecy 可以提高安全性;
通过 Wireshark 抓包可以清楚地看到 False Start 带来的好处(服务端的 ChangeCipherSpec 出现在 158 号包中,但在之前的 155 号包中,客户端已经发出了请求,相当于 TLS 握手只消耗了一个 RTT):
Certificate
TLS 的身份认证是通过证书信任链完成的,浏览器从站点证书开始递归校验父证书,直至出现信任的根证书(根证书列表一般内置于操作系统,Firefox 自己维护)。站点证书是在 TLS 握手阶段,由服务端发送的。
Certificate-Chain
配置服务端证书链时,有两点需要注意:1)证书是在握手期间发送的,由于 TCP 初始拥塞窗口的存在,如果证书太长可能会产生额外的往返开销;2)如果证书没包含中间证书,大部分浏览器可以正常工作,但会暂停验证并根据子证书指定的父证书 URL 自己获取中间证书。这个过程会产生额外的 DNS 解析、建立 TCP 连接等开销,非常影响性能。
配置证书链的最佳实践是只包含站点证书和中间证书,不要包含根证书,也不要漏掉中间证书。大部分证书链都是「站点证书 - 中间证书 - 根证书」这样三级,这时服务端只需要发送前两个证书即可。但也有的证书链有四级,那就需要发送站点证书外加两个中间证书了。
通过 Wireshark 可以查看服务端发送的证书链情况,如下图。可以看到本站发送了两个证书,共 2270 字节,被分成 2 个 TCP 段来传输。这已经算小的了,理想的证书链应该控制在 3kb 以内。
ECC Certificate
如果需要进一步减小证书大小,可以选择 ECC(Elliptic Curve Cryptography,椭圆曲线密码学)证书。256 位的 ECC Key 等同于 3072 位的 RSA Key,在确保安全性的同时,体积大幅减小。下面是一个对比:
对称加密 Key 长度 |
RSA Key 长度 |
ECC Key 长度 |
80 |
1024 |
160 |
112 |
2048 |
224 |
128 |
3072 |
256 |
192 |
7680 |
384 |
256 |
15360 |
521 |
如果证书提供商支持 ECC 证书,使用以下命令生成 CSR(Certificate Signing Request,证书签名请求)文件并提交给提供商,就可以获得 ECC 证书:
openssl ecparam -genkey -name secp256r1 | openssl ec -out ecc.key
openssl req -new -key ecc.key -out ecc.csr
以上命令中可以选择的算法有 secp256r1 和 secp384r1,secp521r1 已被 Chrome 和 Firefox 抛弃。
ECC 证书这么好,为什么没有普及呢?最主要的原因是它的支持情况并不好。例如 Windows XP 不支持,导致使用 ECC 证书的网站在 Windows XP 上只有 Firefox 能访问(Firefox 证书那一套完全自己实现,不依赖操作系统)。另外,Android 平台也只有 android 4+ 才支持 ECC 证书。所以,确定使用 ECC 证书前需要明确用户系统分布情况。
Session Resumption
另外一个提高 TLS 握手效率的机制是会话复用。会话复用的原理很简单,将第一次握手辛辛苦苦算出来的对称密钥存起来,后续请求中直接使用。这样可以节省证书传送等开销,也可以将 TLS 握手所需 RTT 减少到一个,如下图所示:
可以看到会话复用机制生效时,双方几乎不怎么交换数据就协商好密钥了,这是怎么做到的呢?
Session Identifier
Session Identifier(会话标识符),是 TLS 握手中生成的 Session ID。服务端可以将 Session ID 协商后的信息存起来,浏览器也可以保存 Session ID,并在后续的 ClientHello 握手中带上它,如果服务端能找到与之匹配的信息,就可以完成一次快速握手。
Session Ticket
Session Identifier 机制有一些弊端,例如:1)负载均衡中,多机之间往往没有同步 Session 信息,如果客户端两次请求没有落在同一台机器上就无法找到匹配的信息;2)服务端存储 Session ID 对应的信息不好控制失效时间,太短起不到作用,太长又占用服务端大量资源。
而 Session Ticket(会话记录单)可以解决这些问题,Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在浏览器端。浏览器如果在 ClientHello 时带上了 Session Ticket,只要服务器能成功解密就可以完成快速握手。
配置 Session Ticket 策略后,通过 Wireshark 可以看到服务端发送 Ticket 的过程:
以下是 Session Resumption 机制生效时的握手情况,可以看到没有发送证书等环节:
测试
Github 上有一个名为 rfc5077 的项目,非常适合用来测试服务端对 Session ID 和 Session Ticket 这两种 TLS 会话复用机制的支持情况。下面简单介绍如何使用这个工具。
首先,安装工具所需依赖(以下所有步骤仅在 Ubuntu 14.04.4 LTS 测试通过):
sudo apt-get install -y pkg-config libssl-dev libev-dev libpcap-dev libgnutls-dev libnss3-dev
然后就可以获取源码,开始编译:
BASHgit clone https://github.com/vincentbernat/rfc5077.git
cd rfc5077/
git submodule init
git submodule update
make
编译完成后,当前目录会出现多个可执行文件。这里我们只会用到 rfc5077-client
,它的用法是这样的:
./rfc5077-client [-p {port}] [-s {sni name}] [-4] host [host ...]
-p
用于指定连接的端口,默认是 443;
-s
用于指定 SNI,如果同 IP 同端口部署了多个 HTTPS 网站,需要通过这个参数指定 SNI;
-4
表示只使用 IPV4 地址;
- 命令最后需要可以跟一个或多个 HOST(域名或 IP);
这个工具会先禁用 Session Ticket 将所有 HOST 都测试一遍,然后开启 Session Ticket 再测试一遍。下面是对本站两个 IP 进行测试的命令:
./rfc5077-client -s imququ.com 114.215.116.12 139.162.98.188
测试结果如下(去掉了部分不重要的信息):
[√] Check arguments.
[√] Solve 114.215.116.12:
│ Got 1 result:
│ 114.215.116.12
[√] Solve 139.162.98.188:
│ Got 1 result:
│ 139.162.98.188
[√] Using SNI name imququ.com.
[√] Prepare tests.
[√] Run tests without use of tickets.
[√] Display result set:
│ IP address │ Try │ Reuse │ SSL Session ID │ Master key │ Ticket
│ ───────────────┼─────┼───────┼────────────────┼────────────┼────────
│ 114.215.116.12 │ 0 │ × │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 1 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 2 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 3 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 4 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 139.162.98.188 │ 0 │ × │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 1 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 2 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 3 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 4 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
[√] Dump results to file.
[√] Run tests with use of tickets.
[√] Display result set:
│ IP address │ Try │ Reuse │ SSL Session ID │ Master key │ Ticket
│ ───────────────┼─────┼───────┼────────────────┼────────────┼────────
│ 114.215.116.12 │ 0 │ × │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 1 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 2 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 3 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 4 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 0 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 1 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 2 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 3 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 4 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
[√] Dump results to file.
从以上结果可以看出:禁用 Session Ticket 时,每次连接到不同 IP 都会导致 Session 无法复用;而启用 Session Ticket 后,不同 IP 之间也可以复用 Session。符合前面的结论。
值得注意的是,为了让一台服务器生成的 Session Ticket 能被另外服务器承认,往往需要对 Web Server 进行额外配置。例如在 Nginx 中,就需要通过 ssl_session_ticket_key
参数让多台机器使用相同的 key 文件,否则 Nginx 会使用随机生成的 key 文件,无法复用 Session Ticket。出于安全考虑,key 文件应该定期更换,并且确保换下来的 key 文件被彻底销毁。
OCSP Stapling
出于某些原因,证书颁发者有时候需要作废某些证书。那么证书使用者(例如浏览器)如何知道一个证书是否已被作废呢?通常有两种方式:CRL(Certificate Revocation List,证书撤销名单)和 OCSP(Online Certificate Status Protocol,在线证书状态协议)。
CRL 是由证书颁发机构定期更新的一个列表,包含了所有已被作废的证书,浏览器可以定期下载这个列表用于验证证书合法性。不难想象,CRL 会随着时间推移变得越来越大,而且实时性很难得到保证。OCSP 是一个在线查询接口,浏览器可以实时查询单个证书的合法性。在每个证书的详细信息中,都可以找到对应颁发机构的 CRL 和 OCSP 地址。
OCSP 的问题在于,某些客户端会在 TLS 握手阶段进一步协商时,实时查询 OCSP 接口,并在获得结果前阻塞后续流程,这对性能影响很大。而 OCSP Stapling(OCSP 封套),是指服务端在证书链中包含颁发机构对证书的 OCSP 查询结果,从而让浏览器跳过自己去验证的过程。服务端有更快的网络,获取 OCSP 响应更容易,也可以将 OCSP 响应缓存起来。
OCSP 响应本身经过了数字签名,无法伪造,所以 OCSP Stapling 技术既提高了握手效率,也不会影响安全性。启用这项技术后,也可以通过 Wireshark 来验证:
可以看到,服务端在发送完证书后,紧接着又发来了它的 OCSP 响应,从而避免了浏览器自己去验证证书造成阻塞。需要注意的是,OCSP Stapling 只能包含一个 OCSP 响应,浏览器还是可能自己去验证中间证书。另外,OCSP 响应本身会占用几 kb 的大小。
OCSP Stapling 功能需要 Web Server 的支持,主流的 Nginx、Apache 和 H2O 都支持 —— 但同时还取决于使用的 SSL 库 —— 例如 BoringSSL 不支持 OCSP Stapling,使用 BoringSSL + Nginx 就无法开启 OCSP Stapling。
如何使用 Nginx 配置本文这些策略,可以参考我之前的文章:本博客 Nginx 配置之性能篇。
最后,强烈推荐 Qualys SSL Labs 的 SSL Server Test 工具,可以帮你查出 HTTPS 很多配置上的问题。本博客的测试结果见这里。
本文一部分内容来自于 Google 性能专家 Ilya Grigorik 写的《High Performance Browser Networking》第四章:Transport Layer Security (TLS)。这是一本可以免费在线阅读,一直都在更新的性能优化好书,本博客多次推荐。本书中文翻译由李松峰老师负责,已经出版,名为《WEB 性能权威指南》。
本文链接:https://imququ.com/post/optimize-tls-handshake.html,参与评论 »
--EOF--
相关资料:
专题「Web 服务器」的其他文章 »
- HTTPS 常见部署问题及解决方案 (Dec 12, 2016)
- 开始使用 VeryNginx (Dec 10, 2016)
- 开始使用 ECC 证书 (Aug 27, 2016)
- 为什么我们应该尽快升级到 HTTPS? (May 16, 2016)
- 本博客 Nginx 配置之完整篇 (Mar 21, 2016)
- 从无法开启 OCSP Stapling 说起 (Mar 13, 2016)
- Certificate Transparency 那些事 (Feb 03, 2016)
- Let's Encrypt,免费好用的 HTTPS 证书 (Dec 25, 2015)
- 从 Nginx 默认不压缩 HTTP/1.0 说起 (Dec 15, 2015)
- 使用 BoringSSL 优化 HTTPS 加密算法选择 (Oct 15, 2015)