编程技术分享平台

网站首页 > 技术教程 正文

8000字带你搞懂网络协议优化-TLS/HTTPS协议优化!

xnh888 2024-09-28 03:05:49 技术教程 194 ℃ 0 评论

TLS/HTTPS协议优化

随着HTTPS协议的应用越来越普及,针对HTTPS协议的大规模部署需要的资源也越来越多。

一方面,由于HTTPS协议多了TLS/SSL握手的流程,因此会使用户感知到请求HTTPS协议比请求HTTP协议要慢。

另一方面,HTTPS协议的加解密计算任务繁重,会需要大量的CPU/服务器资源。本节将介绍TLS/HTTPS协议的优化方式。我们首先介绍一下TLS/HTTPS协议性能问题的来源,然后会对TLS/HTTPS协议性能问题的4种优化方法进行详细介绍,最后针对目前公有云的普及所造成的安全问题做的安全方面的优化方案进行介绍。

6.2.1 TLS/HTTPS协议的性能问题

HTTP和HTTPS都是基于TCP的协议,可以参考图6-3对HTTPS/TLS1.2协议完全握手的流程有一个大致的了解。

从图6-4中我们可以知道,HTTPS协议的完全握手要比HTTP协议的完全握手多两个RTT,这就不难理解HTTPS协议为什么“慢”了。下面来分析一下TLS1.2协议完全握手的具体流程。

(1)客户端向服务器端发送Client_hello报文,带有随机数C(用于后续的密钥生成)、SSL/TLS版本、支持的加密算法、密钥交换算法等信息。

(2)服务器端向客户端响应Server_hello报文,带有随机数S(用于后续的密钥协商)、SSL/TLS版本,加密套件列表。

(3)服务器端将自己的证书发送给客户端,同时服务器端可能通过发送证书请求(Certificate Request)要求客户端也要发送自己的证书给服务器端。

(4)客户端收到服务器端的证书后会检测证书是否合法,如果不合法,则停止SSL/TLS握手;如果合法,则获取服务器端的公钥。如果收到服务器端的证书请求,则将客户端的证书发送给服务器端。

(5)如果服务器端收到客户端的证书,则也会去验证客户端的证书。如果不合法,则停止SSL/TLS握手,如果合法,则会获取客户端的公钥。

(6)客户端生成随机数Pre-master,利用证书提供的公钥加密,发送给服务器端。Pre-master加上随机数C和S,客户端可以通过计算得到密钥。客户端向服务器端发送利用此密钥加密发送的握手信息。

(7)服务器端用证书的私钥解密Pre-master,加上随机数C和S,也可以得到后续通信使用的密钥。服务器端收到客户端的加密握手信息后,解密完成验证。如果验证通过,则服务器端向客户端发送一段利用协商的密钥加密后的握手信息。

至此,握手完成,后续都采用协商的密钥进行加密/解密。第6步和第7步就是非常重要的非对称加密实现的步骤。

SSL/TLS的非对称加密算法有RSA和ECDHE。这两类算法的计算量都很大,尤其是RSA非常消耗CPU资源。这也是SSL/TLS性能低下的重要原因,对称加密实际上对性能的影响很小。目前,对RSA和ECDHE两种常见的非对称加密算法的分析有很多参考资料,读者可自行查阅,本节不再赘述。

6.2.2 Session ID及Session Ticket

无论是提高吞吐量还是降低延时,都可以通过减少SSL/TLS握手次数的方法来实现。如果来自相同客户端的HTTPS请求都可以复用之前的Session,岂不美哉?

SSL握手会在客户端和服务器端存有Session,这个Session就是用来保存客户端和服务器端之间的SSL握手记录的。这种握手记录的方式通常有两种,客户端常见的是Session Ticket,服务器端常见的是Session ID。

当HTTPS请求复用Session ID或Session Ticket时,客户端和服务器端交互流程如图6-5所示。

从图6-5中我们可以看出,复用Session可以减少HTTPS协议握手的1个RTT时间。这对降低延时有很大的帮助。另外,由于复用了Session,降低了非对称加密的计算量,因此可以极大地减轻服务器端的负载,提高吞吐量。

对于Session ID来说,如果客户端要复用它,则要求之前的完全握手客户端和服务器端存储这个Session及其对应的Session ID。一般是在完全握手的Server hello报文中携带Session ID,让客户端记住这个Session,只要下一次客户端的Client hello报文携带了这个Session ID,服务器端就会根据Session ID去查找Session。如果查找到了,则可以达到Session复用的效果,如果找不到,则继续走完全握手的流程。

Session Ticket是在之前完全握手阶段,服务器端通过一定的加密计算方法(一般用Ticket Key)将一个会话Ticket传输给客户端的,客户端会维持这个Session的所有信息,服务器端本身不再存储Session的完整信息。

当客户端在Client hello报文复用Session Ticket后,服务器端会解密Session Ticket的信息(一般用Ticket Key),从而获取完整的Session信息,达到Session复用的效果。这里需要注意的是,如果使用Ticket Key实现Session Ticket的复用,则这个Ticket Key显得尤其重要。如果黑客获取了Ticket Key的信息,他就可以利用这个信息解密之前的会话内容,这显然不是安全的行为。所以,为了确保HTTPS协议的安全性,Ticket Key需要经常变化。

下面以常见的Nginx为例说明在工程上如何通过Session ID和SessionTicket实现Session复用。

Nginx本身就支持Session ID和Session Ticket。

开启Session ID:由于Nginx是多进程模型,每个进程都有独立的内存空间,所以需要配置保存Session的全局共享内存。另外,还要设置Session的缓存老化时间,代码如下:

开启Session Ticket:Session Ticket需要通过一个Ticket Key开启。这个Key一般可以用OpenSSL来生成,代码如下:

国内很多互联网公司的Nginx服务器都在负载均衡设备的后面,当以这样的架构上线时,很难达到Session复用的效果,如图6-6所示。

以上述方式部署的Nginx集群容易导致缓存未命中(cache miss)的问题出现。假如Nginx A存有和客户端交互的Session,由于负载均衡的作用,客户端的下一次请求可能会落到Nginx B或Nginx C上,会使Nginx无法根据SessionID查找到对应的Session,也就无法起到复用的效果。如果采用SessionTicket的方式达到Session复用的效果,也必须使Nginx A、Nginx B、Nginx C的Ticket Key保持一致。

想要使Ticket Key在Nginx集群之间保持一致并不困难,毕竟它和数据面不耦合,我们大可以周期性地生成Key,再推送到Nginx集群,对Nginx集群进行配置重载。

但是,Session ID如何在Nginx集群之间保持一致呢?它和数据面高度耦合,Nginx集群之间的Session每时每刻可能都在变化。在这种场景下,我们很容易想到使Nginx集群共享一个全局Session数据库和Ticket Key的变化库。另外,由于Session的全局查找是一个远程操作,因此这种查找时间必然很可观,如果是同步阻塞型查找,那么网络I/O等待事件会导致Nginx的吞吐量大幅下降,所以这里采用异步查找的方式。Nginx一般用OpenSSL来完成TLS/SSL相关的握手。

其中,OpenSSL自1.1.0版本开始,便支持了异步操作,该异步操作是基于内部实现的Async job(协程 ) 进 行 的 。 当 TLS/SSL 握 手 采 用 异 步 模 式 进 行 时 , 就 会 调 用ASYNC_start_job,同时保留这个进程当前的堆栈信息,然后切换到进程去进行一些类似I/O操作。操作完毕后,用户需要通过原来的进程去主动查询Asyncjob的状态。如果状态是ASYNC_FINISHED,则切换到原来的堆栈,继续后面的操作。

在这里,我们通过OpenSSL这种异步特性实现了一套基于OpenSSL Asyncjob的全局Session远程查找Nginx模块,该模块实现的前提是Nginx要支持Async job。

它的主要思想就是创建一个远程的集群共享数据库,存储TLS/SSL的Session信息,并利用OpenSSL提供的SSL_CTX_sess_set()接口函数注册一些回调函数,包括新建、获取及删除TLS/SSL Session。

新建Session的操作一般需要将该Session在本地共享内存区存储一次后,再在远程的数据库内存储一次;获取Session的操作需要先在本地共享内存区查找,如果找不到再去远程的集群共享数据库查找;删除操作实际上可以不用实现,本地内存区和远程集群共享数据库的过期机制可以使得Session在配置的过期时间后自行超时老化。上述远程数据库操作都要在OpenSSL的异步框架内实现。目前,我们还没有开源这个模块,更多关于Nginx对于OpenSSL的异步支持可以参考Intel的Nginx(见链接[15])或淘宝的Tengine(见链接[16])。

Nginx集群通过远程集群共享数据库进行Session共享的具体实现架构如图6-7所示。

6.2.3 False-Start

TLS的False-Start由Google率先提出。具体的做法就是在客户端发送change_cipher_spec的同时,不用等待服务器端响应change_cipher_spec,就去发送加密应用数据。

从图6-8中我们可以看到,服务器端开启了False-Start后,SSL握手可以节约1个RTT的交互时间。False-Start需要客户端和浏览器同时满足条件,像Chrome和Firefox这样的客户端需要支持NPN/ALPN,Safari客户端从OSX 10.9版本开始支持False-Start;对于服务器端来说,只要开启支持前向安全即可。在Nginx上开启False-Start只要配置如下字段:

6.2.4 TLS1.3协议

TLS1.3协议是对TLS/SSL协议的最新优化,其证书规范于2018年8月落地。

OpenSSL从1.1.1版本开始支持TLS1.3协议。Nginx从1.13.0版本开始正式支持TLS1.3协议。实际上,Nginx的支持也依赖于OpenSSL对TLS1.3协议的支持。

TLS1.3协议与之前的TLS1.2协议相比有很大的改进,其改进内容如下。

● 引入新的密钥协商算法。

● 对比TLS1.2协议的TLS/SSL握手,可以实现0-RTT的数据传输。

● 废弃了一些加密算法,如对于之前的TLS来说重要的RSA加密算法,现在可以采用前向安全的Diffie-Hellman算法进行握手。

● 只有Client hello报文和Server hello报文是明文传输,其余所有报文都是加密的,大大增加了安全性。

● 不允许加密报文压缩,不允许重新协商(renegotiation)。

● 不再使用DSA证书。

总体来说,TLS1.3协议在性能和安全方面做了很大的改进,是一种和之前的TLS设计理念完全不同的协议。

图6-9所示为TLS1.3协议下的HTTPS交互。其中,“+”表示上一条消息的扩张,“*”表示可选数据,“{}”表示利用握手的中间密钥加密,“[]”表示利用最终的协商密钥加密。

下面来介绍一下TLS1.3协议的握手流程。

(1)客户端向服务器端发送Client hello,包含客户端的协议版本、Session ID、密码套件、压缩算法及扩展消息。

(2)服务器端向客户端回复Server hello,包含选定的加密套件、证书、签名后的握手消息,并利用客户端提供的参数生成临时公钥,结合选定参数计算共享密钥,服务器端生成的临时公钥可以通过key_share的扩展来发送。

(3)当客户端收到key_share消息后,使用证书公钥进行验证,获取临时公钥,生成会话锁需要的共享密钥。

(4)双方利用共享密钥进行加密/解密传输。

显然,TLS1.3协议只需要1个RTT即可完成TLS的握手,比TLS1.2协议的握手速度更快。在加密算法上,由于不再有RSA的非对称加密,CPU的负载必然会下降。

另外,TLS1.3协议还支持一种预共享密钥模式(Pre-Shared Key,PSK)的握手,它类似于早期TLS的版本基于Ticket Key的Session复用的握手方式。

在这种场景下发送的Client hello报文需要携带psk_key_exchange_modes和pre_shared_key拓展,Server hello报文需要携带pre_shared_key拓展。

TLS1.3协议的优点还在于,在一定的场景下可以支持0-RTT的传输,即客户端可以在TCP协议完成握手后直接发送应用层的数据,这种0-RTT传输所需满足的条件如下。

● 之前已经有过一次完整的握手,并且在结束后,服务器端发送了Session Ticket。在Session Ticket中存在max_early_data_size拓展,表示愿意接收early_data。

● 当第二次握手时,客户端将pre_shared_key发送给服务器端,服务器端从pre_shared_key中恢复Session。

● 当第二次握手时,客户端发送early_data数据包。

● 当第二次握手时,服务器端支持读取early_data。

即0-RTT实际上是以PSK握手为前提的。在0-RTT传输场景下,HTTPS交互如图6-10所示。

( 1 ) 客 户 端 向 服 务 器 端 发 送 Client hello 及 如 下 信 息 :

psk_key_exchange_modes拓展、early_data拓展、change cipher spec消息(非必须)、ApplicationData(应用层数据)。

(2)服务器端向客户端发送Server hello及如下信息:change cipherspec消息(非必须)、EncryptedExtensions消息、early_data拓展(表示愿意 接 收 客 户 端 Client hello 携 带 的 early_data ) 、 Finished 报 文 、ApplicationData(应用层数据)。

(3)客户端向服务器端发送EndOfEarlyData报文、Finished报文及ApplicationData(应用层数据)。

(4)服务器端向客户端发送New Session Ticket(非必须)。

实际上,有可能出现PSK握手失败或服务器端不接收0-RTT请求的情况。在这样的场景下,握手会从0-RTT传输降级到1-RTT传输。

在Nginx下支持0-RTT的握手需要配置ssl_early_data on。

6.2.5 硬件加速卡和计算分离

在6.2.2节,我们了解到TLS/SSL需要消耗大量的CPU资源进行加解密运算,这很容易导致服务器的CPU达到性能瓶颈。很多芯片厂商提供了硬件加速卡,专门用于进行加解密运算,拥有很高的性能。其中,以Intel的QAT卡为代表,被大量互联网公使采用。6.2.2节提到的Intel的QAT支持异步的Nginx,就是适配在QAT卡场景下的Nginx。

在Linux场景下,硬件加速卡一般要提供相应的驱动和系统调用的接口,供用户态程序调用并进行加解密运算。基于OpenSSL的Engine机制,Intel提供了一套QAT的加速卡软件包(见链接[17])。

这里简单介绍OpenSSL的Engine机制,它是一种可以为开发者提供自定义加解密接口的框架。开发者可以自己实现一套常用的加解密接口,注册到OpenSSL中,然后编译成动态库,存放到OpenSSL的指定目录中,再对OpenSSL的配置文件openssl.cnf进行配置,指定Engine。OpenSSL在调用初始化函数过程中,会读取配置文件,根据配置文件指定的动态库查找注册的加解密接口函数,具体可以参考OpenSSL官方给出的实例。这样,对于OpenSSL的上层应用来说,调用的接口是透明的,应用程序无须关心OpenSSL内部具体的加解密运算到底是CPU还是加速卡来负责。下面是一种常见的QAT卡进行加解密的OpenSSL配置实例,代码如下:

需要注意的是,这里的default_algorithms表示哪些算法会调用qat.so动态库提供的接口,上文的配置只分离了RSA算法。这是因为一些对称加密的算法调用很频繁,如果每次都经过系统调用去让加速卡计算,那么虽然CPU的负载低了,但是系统调用的成本却变高了,在内核态和用户态之间过于频繁地切换并不是一件好事。实际上,分离哪些算法并没有一定的说法,在使用加速卡的场景下,最好可以根据实际情况进行实验,得到较优的经验值。

另外,我们知道非对称加解密非常复杂,计算量非常大,尤其是RSA算法会消耗大量的计算时间。很显然,如果是同步计算,那么调用加速卡进行加解密后,吞吐量性能瓶颈可能不在计算量上,而在计算的时间上。如何解决这个问题呢?大家很容易想到使用异步计算。前文提到了支持异步计算的OpenSSL和Intel的异步版本的Nginx。实际上,这两个软件本身就是为加速卡异步计算打造的。

通过性能测试,在相同的硬件条件和测试环境下(Nginx服务器有32个逻辑CPU核心),利用QAT卡进行异步计算的Nginx服务器的QPS在HTTPS完全握手的场景下大约可以达到2.5万,而原生的Nginx的QPS在HTTPS完全握手的场景下大约可以达到1.5万。

我们使用OpenSSL的命令接口去测试QAT卡的RSA计算的性能,发现一张卡的计算能力可以达到4万左右。这样我们就会发现QAT卡的性能没有被充分利用。我们能否让其他服务器也去使用这剩余的计算能力呢?更进一步来说,我们能否构建一个专用的集群,集群中的每台服务器都能安装多张QAT卡,然后用大量的Nginx服务器远程调用这个QAT卡集群呢?再进一步来说,我们能否把交给QAT卡计算的任务通过网络交给远程的空闲服务器呢?基于上述目的,我们采用了计算分离的方案。

计算分离的大致思路就是实现一个类似qat.so的OpenSSL的异步Engine。

我们将这个Engine命名为Remote_Engine。其内部有一些加解密算法的实现,如RSA加解密的接口。这些加解密的接口都会把加解密的参数通过RPC远程传输给专门的计算集群,然后异步返回;计算集群计算完毕后返回响应,再继续执行之前的握手流程。上述异步式远程调用Remote_Engine进行加解密的流程需要在异步的Nginx基础上实现,远程调用的异步是基于RPC的异步和OpenSSL的Async job框架来实现的。计算集群可以是插入多张QAT卡的集群,也可以是CPU空闲的集群(如冷存储的服务器),还可以是空闲的Docker资源。

这种灵活部署的计算集群可以极大地节约CPU资源。计算集群的服务发现机制可以由ZooKeeper提供。

图6-11所示为计算分离的框架,CPU部分表示最终使用CPU计算加解密的集群节点。它们的部署比较灵活,如晚高峰时,可以使视频转码任务量较小的集群和冷存储的集群加入线上。

6.2.6 自动化数字证书管理

HTTPS(Hypertext Transfer Protocol Secure,超文本传输安全协议)是一种安全通信的传输协议。HTTPS通过HTTP进行通信,但利用SSL/TLS加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护用户交换资料的隐私性与完整性。HTTPS协议由Netscape在1994年提出,随后扩展到互联网上。

HTTPS协议的主要作用是在网络上创建一个安全信道,当服务器证书被验证和被信任时,可以防止网络窃听、中间人攻击。。

服务器证书是由数字证书认证机构(Certificate Authority,CA)签发的。数字证书包含密钥对(公钥和私钥)所有者的识别信息,通过验证识别信息的真伪实现对证书持有者身份的认证。简单来说,公钥是公开的,私钥需要妥善保存。一旦丢失私钥,可能导致网站信息泄露等。

对于基于企业内网部署的HTTPS服务,除非OpenSSL库存在严重漏洞(如2014年的Heartbleed bug),否则外部攻击者很难直接获取证书私钥文件。但随着企业内部业务越来越大与外部的特殊合作场景,需要一种安全有效的证书管理方法与证书安全分发机制确保证书在部署过程中的安全性。

对于上述应用场景,我们可以构建证书管理平台,针对服务器证书私钥进行特殊的加密运算,并把密文保存在证书管理平台中,不涉密的文件明文保存。对业务服务器或七层负载均衡器进行改造,使其支持从证书管理平台远程下载证书并保存在内存中,如图6-12所示。

在一般情况下,我们不用每次SSL握手都去请求证书管理平台,这样效率不高且整体SSL延迟变大。我们通常在启动业务服务器或七层负载均衡器后远程获取证书参数,然后保存在本地内存中。考虑到因部分场景的网络问题有可能导致访问证书管理平台失败,我们会在本地缓存一份加密的证书文件,以便在远程访问证书管理平台失败时,可以读取本地的缓存文件。

证书管理平台的存在方便了我们统一管理证书。在证书过期之前,我们可以更新证书管理平台上相应的证书,业务服务器或七层负载均衡器只要周期性地获取远程证书即可。

6.3 HTTP协议优化和HTTP2.0

目前,HTTP通信主要是基于HTTP1.1进行的,但随着互联网行业的迅速发展,HTTP1.1暴露的问题也越来越多,其性能瓶颈主要表现在以下4个方面。

● 同一时刻,每个连接只能发送一个请求。

● 请求仅支持从客户端发起。

● 请求/响应头部未经压缩就发送,会增加网络延时。

● 请求/响应头部冗长,浪费通信资源。

为了解决HTTP1.1的性能瓶颈问题,缩短Web页面的加载时间,出现了HTTP2.0,该协议针对原HTTP1.1的性能瓶颈主要做了以下4个方面的改进。

1.二进制分帧(Binary Format)

针对HTTP1.1的高延迟和通信资源紧张的问题,HTTP2.0引进了二进制分帧层,如图6-13所示。HTTP2.0在二进制分帧层上将所有传输信息分为更小的消息和帧,并采用二进制格式的编码将其封装。新增的分帧层能够兼容HTTP1.1标准,将HTTP1.1中的头部信息封装到HTTP2.0的HEADERS帧,请求体封装在DATA帧。

2.多路复用(Multiplexing)

当同一域名下的请求数受限时,在HTTP1.1中每个连接只发送一个请求的问题就会使很多请求因为域名请求数限制而得不到处理,或者需要使用更多的域名资源来分担请求。HTTP2.0引入了多路复用,该机制允许同时通过单一的HTTP连接发起多重的请求-响应消息,这样就可以实现多流并行而不用依赖建立多个TCP连接(见图6-14)。

同时,每个数据流都可以被拆分成很多互不依赖的帧,而这些帧可以交错,也可以分优先级。最后,可以在另一端把它们重新组合起来。这在一定程度上避免了由冗长的头部引起的通信资源浪费。

3.头部压缩(Header Compression)

HTTP2.0会对HTTP头部信息进行一定的压缩,为原来每次通信都要携带的大量头部信息信息(“键-值”对)在两端建立一个索引表,对相同的头部信息只发送索引表中的索引。这样既避免了重复HTTP头部信息的传输,又减小了需要传输的大小。如果HTTP头部信息发生了变化,那么只需通过HEADERS帧发送变化了的数据。新增或修改的HEADERS帧会被追加到头部索引表中,如图6-15所示。头部索引表在HTTP2.0的连接存续期内始终存在,由客户端和服务器端共同更新。

4.服务器端推送(Server Push)

在HTTP1.1中,当浏览器请求一个网页时需等待服务器端返回HTML,对HTML进行解析后才能发送所有内嵌资源的请求。

在浏览器发送了资源的请求后,服务器端才会返回对应的JavaScript代码、图片和CSS。

HTTP2.0的服务器端推送的工作就是,当服务器端在收到客户端对某个资源的请求时,会判断客户端可能还要请求其他资源,然后将这些资源一同发送给客户端。客户端可以选择把额外的资源放入缓存中,也可以选择发送RST_STREAM frame拒绝所有它不想要的资源。通过服务器端推送可以提高HTTP协议的数据传输性能,大概会比HTTP1.1快几百毫秒,提升程度不是特别多,所以不建议一次推送太多资源,这样反而会可能因为不必要的数据推送降低HTTP协议的数据传输性能。

本文给大家讲解的内容是网络协议优化-TLS/HTTPS协议优化

  • 下篇文章给大家讲解是网络协议优化-基于UDP的传输协议优化、DNS协议优化

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表