Nginx https性能优化

1、影响HTTPS速度的主要原因:

      众周所知网站启用https后,会加剧服务器的负担。传统的http使用TCP三次握手建立连接,而SSL和TLS在这个基础上还需要9个握手包,所以这个负担显而易见。

1.1 密钥交换算法

常见的密钥交换算法有 RSAECDHEDHDHE 等算法。它们的特性如下:

RSA:算法实现简单,诞生于 1977 年,历史悠久,经过了长时间的破解测试,安全性高。缺点就是需要比较大的素数(目前常用的是 2048 位)来保证安全强度,很消耗 CPU 运算资源。RSA 是目前唯一一个既能用于密钥交换又能用于证书签名的算法。

DH:diffie-hellman 密钥交换算法,诞生时间比较早(1977 年),但是 1999 年才公开。缺点是比较消耗 CPU 性能。

ECDHE:使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA 相同的安全等级。缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。

ECDH:不支持 PFS,安全性低,同时无法实现 false start。

DHE:不支持 ECC。非常消耗 CPU 资源 。

1.2 建议优先支持 RSA 和 ECDH_RSA 密钥交换算法。

原因是:

1, ECDHE 支持 ECC 加速,计算速度更快。支持 PFS,更加安全。支持 false start,用户访问速度更快。

2, 目前还有至少 20% 以上的客户端不支持 ECDHE,我们推荐使用 RSA 而不是 DH 或者 DHE,因为 DH 系列算法非常消耗 CPU(相当于要做两次 RSA 计算)。

1.3 更改其配置如下

(参照 Nginx Performance Tuning for SSL( http://dojo.techsamurais.com/?p=1384 ):

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

或者

ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:!RC4-SHA:HIGH:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!CBC:!EDH:!kEDH:!PSK:!SRP:!kECDH;

(禁用了RC4,sha1,MD5等算法)

2 辅助加速:

2.1. 启用SPDY

SPDY 是 Google 推出的优化 HTTP 传输效率的协议( https://www.chromium.org/spdy ), 它基本上沿用了 HTTP 协议的语义, 但是通过使用帧控制实现了多个特性,显著提升了 HTTP 协议的传输效率。
SPDY 最大的特性就是多路复用,能将多个 HTTP 请求在同一个连接上一起发出去,不像目前的 HTTP 协议一样,只能串行地逐个发送请求。
可以 在编译Nginx带上参数 –with-http_spdy_module 支持SPDY协议,然后可在配置中启用:

listen 443 ssl spdy;

检测是否使用SPDY的网址( https://spdycheck.org/ )

2.2. HSTS

HSTS(HTTP Strict Transport Security)。服务端返回一个 HSTS 的 http header,浏览器获取到 HSTS 头部之后,在一段时间内,不管用户输入 www.baidu.com 还是 http://www.baidu.com ,都会默认将请求内部跳转成 https://www.baidu.com;
将下述行添加到你的 HTTPS 配置的 server 块中:

add_header Strict-Transport-Security "max-age=31536000";

2.3. Session cache

Session cache 的原理是使用 client hello 中的 session id 查询服务端的 session cache, 如果服务端有对应的缓存,则直接使用已有的 session 信息提前完成握手,称为简化握手。

Session cache 有两个缺点:

1, 需要消耗服务端内存来存储 session 内容。

2, 目前的开源软件包括 nginx, apache 只支持单机多进程间共享缓存,不支持多机间分布式缓存,对于百度或者其他大型互联网公司而言,单机 session cache 几乎没有作用。

Session cache 也有一个非常大的优点:session id 是 TLS 协议的标准字段,市面上的浏览器全部都支持 session cache

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 20m;

参照Nginx的官方文档1MB内存大约可以存储4000个session,按例配置20M大约可以存储80000。根据需求合理设置。

-----------------

https存在一个缺点:每次新的TLS连续都需要握手,以便创建共享的加密密钥,在TCP三次握手之上还需要两个来回。

但是TLS有几个特点可以抵消额外的握手:重用一个Session。有两个标准会话重用机制:session IDs (RFC 5246) 和 session tickets (RFC 5077),使用其中一个技术,一个客户端可以重用之前创建的会话,这个会话是之前和服务器进行握手成功的,这样可以减少一次来回过程。基于SessionID的会话重用适合现代所有浏览器,FireFox和Chrome甚至还支持 session tickets。

Nginx之ssl_session_cache详解:

引用一下Nginx官网对ssl_session_cache选项的说明

ssl_session_cache

翻译过来:

Syntax:ssl_session_cache off | none | [builtin[:size]] [shared:name:size];

默认:

ssl_session_cache none;

Context:http, server

设置存储session参数的缓存的类型和大小。缓存可以是以下任何一种类型:

off

严禁使用session缓存:nginx明确告诉客户端session可能不会被重用。

none

session缓存的使用被禁止:nginx告诉客户端session可能会被重用,但实际上并不会将session参数存储在缓存中。

builtin

在OpenSSL中构建的缓存;仅由一个工作进程使用。缓存大小在session中指定。如果没有给出大小,则等于20480个会话。使用内置高速缓存可能导致内存碎片。

shared

所有工作进程之间共享缓存。缓存大小以字节为单位指定;一兆字节可以存储大约4000个session。每个共享缓存都应该有一个任意名称。具有相同名称的缓存可以用于多个虚拟服务器。

两种类型的缓存可以同时使用:配置案例:

ssl_session_cache builtin:1000 shared:SSL:10m;

但是只使用shared缓存,而不使用built-in缓存性能应该会更高。

Nginx配置ssl_session_cache:

目前使用较多的配置是built-in和shared同时使用:

ssl_session_cache builtin:1000 shared:SSL:10m;

但是Nginx官方说只使用shared,性能会更高,配置方法为:

ssl_session_cache shared:SSL:10m;

 

2.4 Ocsp stapling

        Ocsp 全称在线证书状态检查协议 (rfc6960),用来向 CA 站点查询证书状态,比如是否撤销。通常情况下,浏览器使用 OCSP 协议发起查询请求,CA 返回证书状态内容,然后浏览器接受证书是否可信的状态。 将证书保存下来,浏览器请求时候直接通过自己的服务器发送回去,防止验证服务器出问题,还能加快访问速度。

查看OSCP验证服务器地址:

openssl x509 -in 1_test.qupeiyin.net_bundle.crt  -text

在输出的文字中找到 OCSP - URI: ,后面的 URL 就是 OSCP 的验证服务器地址。
如图:

请求OSCP证书

openssl ocsp -noverify \             
-issuer /certificate-path/trustchain.crt \             
-cert /certificate-path/trustchain.crt \             
-url http://ocsp1.wosign.com/ca6/server1

不出意外会收到如下的结果

trustchain.crt: good        
    This Update: Oct 18 17:59:10 2014 GMT        
    Next Update: Oct 18 23:59:10 2014 GMT

如果出现 403 错误,那就需要在 Header 请求头加上域名参数 -header “HOST” “ocsp2.globalsign.com” ,没问题后就可以直接保存下来证书文件,完整的命令如下:

openssl
ocsp -noverify  -issuer 1_root_bundle.crt
-cert 1_root_bundle.crt -url http://ocsp1.wosign.com/ca6/server1  -header "HOST"
"ocsp1.wosign.com" -text -respout ./stapling_file.ocsp

将保存下来的 stapling_file.ocsp 证书添加到 nginx 的配置中,如下,Nginx 中配置变成了这样子:

ssl_stapling on;
ssl_stapling_verify on;
ssl_stapling_file /stapling_file.ocsp;
ssl_trusted_certificate /certificate-path/trustchain.crt;

这样子重启 Nginx 后就会生效,可以使用下面的命令测试生效结果:

echo QUIT | openssl s_client -connect blog.alphatr.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:' | grep -B 17 'Next Update'

看到 OCSP Response Status: successful 这样的字样就是成功了。
ocsp证书有效期很短,大概不到一个月,所以过段时间要更新ocsp证书,不然还是会验证失败。需要用脚本定时更新OCSP证书。

3. 总结:(全部优化参数)

ssl on;
ssl_certificate /data/www/ssl/ssl.crt;
ssl_certificate_key /data/www/ssl/ssl.key;
ssl_trusted_certificate /data/www/ssl/trustchain.crt;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
resolver 223.5.5.5 223.6.6.6 valid=300s;
resolver_timeout 10s;

error_page 497 https://$host$request_uri;

参数详解:

ssl on  开启SSL
ssl_certificate  对应单张证书
ssl_certificate_key  对应私钥
ssl_trusted_certificate  对应信任链(即Startcom SSL或者Positive SSL中需要附加到单张证书后面的那两张证书,可以独立出来)
ssl_protocols  支持的SSL协议标准(nginx默认参数为:ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;)
ssl_ciphers  (nginx默认参数为:ssl_ciphers HIGH:!aNULL:!MD5;)

ssl_prefer_server_ciphers On; #指定服务器密码算法在优先于客户端密码算法时,使用SSLv3和TLS协议。

error_page 497 https://$host$request_uri;  通过497错误将http转跳到https

 

4. 其他文章:这里记录一下,方便自己学习回顾

转自https://zhuanlan.zhihu.com/p/25290538

这次分享的内容主要是来自于我们HTTPS上具体的工作实践,主要内容分以下三部分:

  1. 计算性能的分析和优化;
  2. 无密钥加载;
  3. 证书优化

4.1 为什么66%的网站不支持HTTPS?

谈优化之前我们先看背景和趋势,大家也很清楚HTTPS是大势所趋,Google、Facebook和国内诸多大型互联网公司也已经支持HTTPS,然而这里有两点大家需要注意:

  1. iOS10的ATS政策(App Transport Security)要求2017年1月1日后所有在iOS App Store上架的App都需要支持HTTPS,否则无法上架;
  2. Google的Chrome浏览器54版本已经将HTTP的域名输入框前增加“!”的提示,如下图,所有的HTTP站点都会有这个标识。同样在2017年1月1日后开始,Chrome浏览器会在用户点击“!”的提示符后将该网站不安全的信息显示出来,只要涉及到登录和搜集用户数据的页面,只要是HTTP的都会标注不安全,相信这也会加速HTTPS的推进。

 

HTTPS很安全,很古老也很成熟,为什么一直到今天我们还有66%的网站不支持HTTPS呢?原因有两点:

1. 慢,HTTPS未经任何优化的情况下要比HTTP慢几百毫秒以上,特别在移动端可能要慢500毫秒以上,关于HTTPS慢和如何优化已经是一个非常系统和复杂的话题,由于时间的关系,本次分享就不做介绍了。但有一点可以肯定的是,HTTPS的访问速度在经过优化之后是不会比HTTP慢;

2. 贵,特别在计算性能和服务器成本方面。HTTPS为什么会增加服务器的成本?相信大家也都清楚HTTPS要额外计算,要频繁地做加密和解密操作,几乎每一个字节都需要做加解密,这就产生了服务器成本,但也有两点大家可能并不清楚:

  • HTTPS有哪些主要的计算环节,是不是每个计算环节计算量都一样?
  • 知道这些计算环节对CPU的影响,我们如何优化这些计算环节?

接下来我将介绍我们在这两个问题上的探讨。

4.2 HTTPS主要的计算环节

首先看HTTPS主要的计算环节,下图是一个协议交互的简要介绍图,它的四种颜色分别代表4种不同的主要计算环节:

  1. 红色环节是非对称密钥交换,通过客户端和服务端不一致的信息协商出对称的密钥;
  2. 蓝色环节是证书校验,对证书的签名进行校验,确认网站的身份;
  3. 深绿色环节是对称加解密,通过非对称密钥交换协商出对称密钥来进行加解密;
  4. 浅绿色环节是完整性校验,不仅要加密还要防止内容被篡改,所以要进行自身的完整性校验。

知道这些主要的计算环节之后,每一个计算环节对计算性能的影响分别是多少以及如何分析?这里和大家分享我们计算性能的分析维度,主要分为三部分:算法、协议和系统。

  1. 算法,所谓的算法其实是HTTPS所用到密码学里最基本的算法,包括对称加密、非对称密钥交换、签名算法、一致性校验算法等,对应的分析手段也很简单:openssl speed;
  2. 协议,因为不同的协议版本和消息所对应使用的算法是不一样的,虽然算法的性能很确定,但是和协议关联起来它就不确定了。由于性能和协议相关,我们重点分析的是协议里完全握手的阶段,我们会对完全握手的每个消息和每个函数进行时间的分析;
  3. 系统,比如我们使用Nginx和OpenSSL,我们会对它进行压力测试,然后在高并发压力环境下对热点事件进行分析和优化。

      接下来详细介绍以上的分析维度,首先是对称加密和一致性校验算法的测试分析,这个手段和工具(openssl speed)很简单,我就不多介绍了。这里总结一下:下图中柱状图越高表示性能越好,可以看出性能最好的是AES-128-GCM,性能最差的是AES-256-CBC,但即使它性能最差,它也只需要47微秒就能处理4000个字节,性能相比来说也还能接受。

      接下来我们看密钥交换和签名算法的测试。

      下表中Sign代表服务端进行的签名,Verify指的是客户端对签名进行的校验。我们关注一下红色数字809,这代表使用RSA-2048位时,我们服务端1秒钟只能处理809次,这已经是我们使用线上非常好的一款CPU进行测试的结果,事实上大部分机器1秒钟只能处理三四百次,可以说性能非常差。

     接下来是Verify校验,能看出来我们使用Ecdsa(nistp256)时一秒钟能处理7000多次,同样这也是我们使用线上比较好的服务器所测试的结果,由于Verify发生在客户端,考虑到移动端手机的CPU是非常弱的,因此这里一秒钟可能只就能处理几百次。

      接下来看协议耗时的分析,这里用ECDHE_RSA非对称密钥交换握手进行举例,大家注意红色ServerKeyExchange部分,它用了2400微秒(2.4毫秒),这是一个非常恐怖的概念,如果我们HTTPS请求每一次都需要进行完全握手处理,这意味着我们CPU一个核每秒最多只能处理400次多一点。

      最后我们看热点事件的分析,它也比较简单,我们对系统进行压力测试,用perf record对事件进行记录,然后使用flame graph将它们可视化出来,最后看到一些相关数据和结果。

总结以上计算性能分析:

  • HTTPS完全握手的性能不到普通HTTP性能的10%,如果说HTTP的性能是QPS 1万,HTTPS可能只有几百;
  • 为什么会这么低呢?主要是RSA算法,它对性能的影响占了75%左右;
  • ECC椭圆曲线如果使用最常用的ECDHE算法,这部分约占整体计算量的7%;
  • 对称加解密和MAC计算,它们对性能影响比较小,是微秒级别的。

4.3 优化

有了这些分析结论,如何优化呢?我们总结了三个步骤:

  1. 首先第一步也是最简单的一个优化策略,就是减少完全握手的发生,因为完全握手它非常消耗时间;
  2. 对于不能减少的完全握手,对于必须要发生的完全握手,对于需要直接消耗CPU进行的握手,我们使用代理计算;
  3. 对称加密的优化;

 

4.3.1 简化握手的原理以及实现

我们首先来看完全握手和简化握手,这是TLS层的概念,我简单说下简化握手的两个好处:

  1. 首先简化握手相比完全握手要少一个RTT(网络交互),从完全握手大家可以看出来,它需要两个握手交互才能进行第三步应用层的传输,而简化握手只需要一个RTT就能进行应用层的数据传输;
  2. 完全握手有ServerKeyExchange的消息(红色框部分),这个消息之前提过需要2.4毫秒,另外完全握手有Certificate证书的消息,而简化握手并不需要。这也就是简化握手第二个好处,它减少了计算量,它不需要CPU消耗太多时间。

 

      既然简化握手这么好,我们如何实现?首先看协议层如何支持。TLS协议层有两个策略可以实现:

  • 第一个是Session ID,Session ID由服务器生成并返回给客户端,客户端再次发起SSL握手时会携带上Session ID,服务端拿到后会从自己的内存查找,如果找到便意味着客户端之前已经发生过完全握手,是可以信任的,然后可以直接进行简化握手。
  • 第二个策略是Session Ticket,同样它也是客户端发起握手时会携带上的扩展,服务器拿到Session Ticket后会对它进行解密,如果解密成功了就意味着它是值得信任的,从而可以进行简化握手,直接传输应用层数据。

工程实现上会有什么问题呢?现在最常用的Nginx+OpenSSL有两个局限:

  1. Nginx只支持单机多进程间共享的Session Cache,假如我们所有接入用的是一台服务器、一台Nginx的话,那ID生成和查找都在一起,肯定是可以命中的,但是我们大部分特别是流量比较大的接入环境都是多台机器接入。比如我们同一个TGW或者说LVS下面有多台Nginx,那么第一台Nginx产生的ID返回给用户,用户可能隔了一个小时候之后再发起SSL握手,它携带上的Session ID肯定会随机地落到某一台Nginx上面(比如落在第三台Nginx上),这样肯定无法查找到之前的Session ID,无法进行简化握手,这是第一个局限,即命中率会比较低;
  2. OpenSSL提供了一个Session Cache的callback可以回调,但是这个回调函数是同步的,而Nginx是完全异步事件驱动的框架,如果Nginx调用这个callback进行网络查找,假如这个网络查找需要1毫秒,这意味着整体性能不会超过一千次。

我们如何进行改进?我们看第一个问题(Session Cache)的两个改进方案:

1. IP Hash

    这是最简单的根据IP做Hash的负载均衡策略,相信大家对此都很清楚,这方案的好处是可以保证相同的IP用户永远都在同一台Nginx上面,Session Cache的命中率会提升,但是它有两个缺点:

  • 容易导致热点,我们有很多Net网关出口IP用户的访问量非常大,也就是说有一些IP请求非常大导致某一台机器负载不均衡;
  • 用户IP可能会经常变化,特别在移动端上,在Wi-Fi和4G环境下切换导致的IP变化同样会使Session Cache的命中率降低。

2. 分布式缓存(分布式Session Cache)

    这是更优的方案,假如用户开始发起握手,我们第一台Nginx生成ID会写入到一个全局的比如redis缓存里,然后返回给用户。用户下一次发起握手的时候,假如他落到第三台Nginx上面,由于我们都是全局的Session Cache查找,这命中率一下就提升上来了。我们实现了这个方案,但暂时还没有开源,在这里可以给大家推荐两个开源方案,大家有兴趣可以了解一下。

  • OpenResty,它提供了SSL Cache全局查找的指令;
  • BoringSSL,这是Google fork OpenSSL的版本,它也在SSL层面上实现了异步的Session Cache查找。

3 Session Ticket

      接下来我们看Session Ticket,由于Session Cache有个缺点是必须在服务端做缓存,会浪费很大内存,而Session Ticket有个好处是它不需要服务端做缓存,但同样它也有个缺点:默认情况下比如三台Nginx各自的Session Ticket加解密密钥是不同(这里的密钥是指Session Ticket的对称加解密的密钥而不是指证书对应的私钥)

      举个例子,比如第一台Nginx的Session Ticket用密钥加密返回给用户,用户下一次再访问落到第三台机器,你用第一台机器加密产生的密钥用第三台的密钥去解密肯定会失败。这个问题很好解决,我们将所有的Nginx机器配置成同一个加解密的密钥就可以了,这样也能实现简化握手

4 Self Session Ticket

      我们看一下第三个方案:Self Session Ticket。Session ID和Session Cache都有一个共同点:Session基于内存,如果我们的App、浏览器或操作系统如果第一次启动或重启(或者浏览器的Tab关闭后又打开)都有可能导致Session ID和Session Ticket丢失,出于安全角度考虑,这情况下就必须要发起完全握手,怎么解决呢?

如果这个App是我们完全自主、独立自主开发,我们可以实现Self Session Ticket,我们将Ticket存储在硬盘里面,而不是在内存里。这显然会带来一定的安全风险,但是我们会做一些限制:

  1. Ticket存储在App的私有路径里,对非Root的手机是很难读取到私有路径的数据;
  2. 我们可以选择对一些安全系数要求不是很高的业务开启这个功能;
  3. 这个密钥开关我们做到可以随时控制。

综上,即使这方案出现最危险的情况,其实是和HTTP方案是一样的,它不会比HTTP方案安全性要差。

 

4.4 异步代理计算的原理和实现

      刚才提到的都是关于如何减少完全握手,提升简化握手,但对很多的请求,比如说浏览器第一次启动必须要经过完全握手,而且不是我们能够自主控制的。对于这部分的内容,完全握手比例占了至少30%以上,也就是说我们还有30%以上的请求必须要触发CPU进行大量计算,对这部分怎么解决呢?

我们的方案是异步代理计算,主要是分三个步骤:

  1. 算法分离,把最消耗CPU资源的算法剥离出来,不让它消耗本机的CPU资源;
  2. 代理计算,既然不消耗本机的CPU资源,我们可以使用硬件加速卡或者空闲的CPU资源来完成计算;
  3. 异步执行,我把算法分离出来交给计算集群去计算的时候,这个过程是异步的,我不需要同步等待计算结果返回,这样对我们性能提升也是非常有帮助的。

4.4.1 算法分离

要分离哪些算法?最主要是密钥交换算法(非对称密钥交换算法),密钥交换算法最常用的是三类:

  1. RSA
  2. ECDHE_RSA
  3. DHE_RSA

由于DHE_RSA是非常消耗性能的,这个方法也不安全,所以我们线上并没有采用。目前使用最多的是ECDHE_RSA,考虑到兼容性的问题时使用了RSA。

RSA和ECDHE_RSA为什么会消耗CPU资源?RSA主要是对客户端发回来的pre_master_secret进行解密,它消耗CPU资源的过程是私钥解密的计算;而ECDHE_RSA则有两个步骤:

  1. 生成ECC椭圆曲线的公钥和几个重要的参数;
  2. 对这几个参数进行签名,客户端要确保参数是我服务端发过来的,就是通过RSA的签名来保证。

RSA签名为什么消耗CPU呢?RSA签名同样有两个步骤:

  1. 首先它通过SHA1进行Hash计算;
  2. 对Hash结果进行私钥加密,也就是最终消耗CPU的过程是私钥解密和私钥加密的计算。这两个计算为什么消耗CPU?看下图公式,如果e或者d这个指数是一个接近2的2048次方的天文数字,那就非常消耗CPU。这也就是RSA算法为什么消耗CPU的最直接的数学解释。

我们再看一下协议层面我们该如何实现分离。同样以ECDHE_RSA为例,由于使用了ECC参数和RSA签名,之前提到的ServerKeyExchange这个消息用了2.4毫秒,我们需要对这个消息进行分离,将一步操作拆成多个步骤。

我们再看一下RSA密钥交换的分离,RSA非对称密钥交换算法不需要ServerKeyExchange的消息,但需要对Client发过来的ClientKeyExchange进行解密的操作,也就是最消耗算法的过程是RSA解密的地方,我们需要对这个步骤进行分离。

4.4.2 异步代理计算——架构

     以上是算法层面包括协议层面进行的分离。接下来解释工程实现上的架构,下图左部分是最常用最简单的配置,比如我们配置好Nginx+OpenSSL,然后把证书和私钥放上去,把443端口打开,就能用上HTTPS了,但这里消耗的都是本机的CPU资源,这里面就会性能很差。

      我们看异步硬件加速卡代理计算的模型,用户发起HTTPS握手,涉及到私钥计算(比如ServerKeyExchange和对ClientKeyExchange解密)的时候,我们会把ECC的参数剥离出来发送给我们的计算集群,发送出去同时立马异步返回,又可以接受其他用户的请求,不需要等待。

      计算集群计算完了以后,我们会把计算结果返回给比如Nginx,Nginx又触发最初的用户场景,从而完成HTTPS的握手,这是一个大概的交互流程。

      这里面需要注意的是,我们不仅仅可以使用SSL硬件加速卡,还可以使用线上的空闲CPU资源,比如CPU比较空闲的存储集群,如果硬件加速卡出现故障,我们可以直接把卡拔掉,可以用硬件加速卡集群上的CPU资源来做计算。我们对算法的解耦是非常纯粹的,和协议、证书没有任何关系,我们只做RSA的计算,不管哪一边进行升级,只要RSA算法还是安全的,我们的协议就十分稳定,我们的维护成本也非常低。

4.4.3 异步代理计算——工程实现

      我们看一下工程实现,像Nginx需要事件框架进行修改来实现OpenSSL Nginx计算集群交互,通过模块是无法实现的,尽管Nginx模块机制非常强大丰富,但是它在HTTP头部数据解析完成之后才能介入处理,但SSL握手只有进行SSL握手完之后才能进行应用层的数据传输,也就是说在进行SSL握手的时候,它没有任何HTTP的数据,这时候模块是没办法介入处理的,必须对事件框架进行修改。

      关于OpenSSL,需要对OpenSSL的协议栈进行修改,主要涉及到s3_srvr.c这个文件,这个文件主要是实现OpenSSL握手的协议栈,这里介绍一个开源方案:OpenSSL1.1.0,它已经支持了异步事件,同样还是需要修改Nginx才能实现异步。性能也很强大,纯ECDHE_RSA性能相比本机能够提升3.5倍,而且解耦做得非常好。

4.5 其他优化

 4.5.1 ECC椭圆曲线的优化

      刚才提到的一系列都是针对完全握手、简化握手的介绍。接下来看对ECC椭圆曲线的优化,这里说的优化不是针对算法本身进行的优化,而是使用OpenSSL的官方版本过程中需要注意的地方。

      尽量使用NIST P-256这条曲线,这条曲线是Intel几个工程师在2013年进行的优化,性能提升了4倍。通常来讲密钥越大,性能肯定越差,比如RSA-2048肯定比1024要慢4倍左右,P-256曲线看上去应该比P-224要慢,但是实际上P-256曲线比P-224性能高了4倍,正是因为经过了一系列的优化。

      另外需要注意的是OpenSSL的版本,这个优化特性只是在1.0.1L之后才加入的,下图表格中OpenSSL的1.0.1e版本ecdh(nistp256)的性能只有2548,而OpenSSL的1.1.0b版本ecdh(nistp256)性能能达到10271,提高了4倍。

4.5.2 对称加密算法的优化

我们再看一下对称加密算法的优化,对称加密主要分两块:

  • 块式对称加密算法,根据刚才OpenSpeed跑的结果也能发现AES-GCM性能最高,建议大家使用。AES-NI同样是Intel CPU提供的硬件加速指令,这个指令相比不开启的CPU性能要提升5倍左右,现在市面上2010年之后的Intel CPU都是支持的,但需要注意如果要直接使用AES对称加解密,一定要使用OpenSSL封装的EVP_EncryptInit_ex函数,而不是用最底层的AES_encrypt(尽管它很好记也很好用),默认的AES_encrypt函数是不会用硬件加速指令的,因此性能会很差;

 

  • 流式对称加密算法。Chacha20-Poly1305是由Google专门针对移动端CPU优化的流式对称加密算法,它的性能相比普通算法要提高3倍。但Chacha20算法只适用于稍微低端、且不支持AES-NI指令的手机才能提高3倍,举个例子,例如iPhone支持AES-NI,但它的性能还是会比AES-GCM要差的。

RC4、SSL3.0已经不安全了,也彻底退出了历史舞台,但我们还有很多客户端只支持SSL3.0,比如IE6,比如WindowsXP的一些客户端和浏览器,我们的业务方仍需要支持这些业务,因此我们就必须支持SSL3.0,那如何支持呢?

 

首先SSL3.0为什么不安全?主要是两点:

  1. SSL3.0存在AES-CBC的缓存Poodle漏洞;
  2. RC4存在Bios偏移的漏洞;

这两个漏洞哪个更严重?Poodle漏洞会更严重一些,于是开启SSL3.0的话我们只支持RC4,这是第一点安全性的考虑,其次RC4的算法性能要比AES-CBC要高,比如业务方一定要支持WindowsXP和IE6的话,优先使用RC4。

4.5.3 无密钥加载

      我们再看无密钥加载,无密钥加载的背景是这样的,HTTPS证书和私钥,私钥是HTTPS安全的根本,如果私钥被泄露了,那么意味着你的HTTPS是没有安全性可言,如果泄露了,只能撤销证书,重新生成私钥。

      而且我们大部分使用HTTPS的场景都是将私钥和证书同机部署在比如Nginx上面,然后私钥保存在硬盘,这其实有安全风险的。比如说我们有很多兼容的大客户,他使用了证书和私钥,但是他的业务可能分散在几个云,或者几个CDN上,他就会担心私钥如果泄露了怎么办?

     我们因此设计了一个无密钥加载的方案,我们先看普通的HTTPS流程,用户发起HTTPS,到达腾讯云,到达STGW,我们直接调用私钥完成计算,完成HTTPS的握手,然后卸载,然后将HTTPS传递给业务。这是最普通的流程,会有一定的安全风险。

      再看一下无密钥加载流程,什么是无密钥呢?比如腾讯云,我们接收的服务器不需要部署私钥,我们私钥是完全放在客户的物理服务器上面,为了保证私钥的安全,客户甚至可以把这个物理服务器放在家里。

      正常的接收流程是这样的,我们用户到达HTTPS握手,到达STGW,然后涉及到私钥计算的时候,我们会把请求以及和私钥相关的参数,封装成一个异步请求发给腾讯云客户的物理服务器,然后物理服务器会调用私钥进行相关计算,再把计算结果返回给腾讯云服务器,完成HTTPS卸载。

      整个交互流程中,STGW或者说腾讯云和私钥没有任何的接触,私钥是客户自己才拥有。这就是我们无密钥加载的一个简单的介绍。当然具体的流程或者实现,其实跟我刚才提到的异步代理计算有点类似,包括协议方面。

 

4.5.4 证书优化

      接下来看证书的优化。如果是个人用户,向大家推荐Let's Encrypt,它是免费开源的CA证书颁发机构,它的优点就是免费开源,开源最大的好处就是可以对协议对整个交互流程都很清楚,支持Debug。它还有一个优点是能支持自动部署,它提供几个工具运行几分钟就可以把证书申请下来。

      但它也有缺点,第一,它只是低级别即域名级别的证书申请,它只对域名的有效性进行校验,你能拥有这个域名就可以给你颁发证书,这就有安全风险了,假如我对域名进行劫持,我就可以冒名申请别人的证书。

      另外兼容性也会比较差,像PC端Chrome访问没有任何问题,但我们用Android7.0去访问就有可能出现问题,因为CA支持和更新并不及时,因此就有兼容性的风险,所以推荐个人用户使用。

      对于企业用户,建议大家使用EV和OV级别的,因为它会需要你证书的机构、你的地址、法人信息、营业执照、在工商局的认证等信息,才确保只有你才能申请这个证书。

      这是云的优势,申请很简单,一键式申请,不需要你自己生成私钥,也不需要生成证书的申请文件。这是腾讯云证书简单的说明。我们还有一个动作,在跟最大的证书厂商进行合作接触,我们会实现自己更低成本的自主品牌证书的颁发,这样的话证书会更加便宜,下图是腾讯云上面的介绍。

      我们看一下证书签名的选择,我们最后使用了RSA,还使用ECDSA,这两个是最主流最常用的签名类型。

      RSA的好处是兼容性好、历史悠久,所有的客户端都支持,因为它算法已经存在40年了,缺点就是之前提到的代理计算,RSA需要私钥进行计算,服务端需要加密解密性能会比较差。

      ECDSA,优点就是服务端的性能好一点,P-256只需要256位的输出域就能实现和2048位长度同样的安全性,它的安全性更高,服务端计算性能要好,但是客户端的性能要差。使用ECDSA P-256客户端的计算量要比RSA要大,RSA的公钥的数字非常小,只有65537,而ECDSA的客户端公钥长度很大,计算性要差很多,特别是手机计算性能要弱。

      ECDSA的缺点就是兼容性差,比如说WindowsXP不支持。具体可以看下图的系统支持情况,这里面其实由于RSA和ECDSA这两个完全不同类型的证书,服务端其实可以同时支持,当然成本可能会稍微增加一些。目前使用ECDSA的还是多一些的。

      最后看一下SHA1和SHA256,SHA1不安全,像Google和Microsoft已经宣布放弃SHA1,如果你使用SHA1证书就访问不了。

      SHA2兼容性比较差,比如WindowsXP不支持SHA2证书,但对于需要支持WindowsXP的业务来说如何兼容呢?我们观察到一个特性:不支持SNI的客户端也不支持SHA2。

      SNI是TLS的一个特性和扩展,发起client hello的时候,它会携带上一个域名的信息,在握手的时候告诉服务端要访问哪一个域名(Server Name Indicator),虽然从协议或者从原理的角度来讲没有任何的必然的关系,但这个现象给我们一个启发:我们通过服务端配置能够解决这个兼容性的问题。

      同样以Nginx和OpenSSL举例,假如说客户端1不支持SNI,只支持SHA1,客户端2是新的系统支持SNI。我们配置配两张证书,第一张证书我们同样都是接403端口和两个不同的Server,证书1放在前面,证书2支持SHA2放在后面。

      当客户端1发起请求的时候,由于它不支持SNI,所以默认会返回第一张证书(第一张证书是支持SHA1的),这样不支持SNI也不支持SHA2的客户端1兼容性没有问题。

      客户端2发起请求的时候,携带了SNI,由于有SNI和域名信息,服务端会挑选有域名的信息SHA2返回给客户端,实现SHA2的兼容。

4.6HTTPS发展

最后是我对于HTTPS发展的简单的概括和理解:

  1. 更广。它会越来越流行,以后会成为互联网的标配。HTTP2作为下一代的协议,主流实现像Chrome\FireFox都是强制使用HTTPS的。ATS也强制使用HTTPS,Chrome2017年也会将HTTP标识为不安全;
  2. 更快。这里涉及到访问速度的优化,TLS1.3是革命性飞跃式的TLS协议,可以认为是TLS2.0,它相比1.2有非常大的变化,最大的变化有完全握手,它只需要一个RTT,而简化握手不需要RTT,也就是应用层数据不需要握手直接可以完成数据的传输,另外QUIC解决HTTPS协议的性能问题,TLS协议以后也会越来越快,大家不用担心访问速度的问题;
  3. 更强。密钥长度越来越大,加密强度会越来越强,随着客户端和硬件的发展,CPU加解密的性能也会越来越强;
  4. 更开放。之前提到类似Let's Encrypt的免费开源方案,以后也会成为越来越流行的方案。

以上是HTTPS的性能优化,谢谢大家!

参考:

https://zhuanlan.zhihu.com/p/25290538

https://www.ioperat.com/news/operation/43.html

https://segmentfault.com/a/1190000017270510

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章