• HTTPS性能优化方案


    1、TLS 握手过程分析

    通过 Wireshark 抓包可以清楚地看到完整 TLS 握手过程所需的两个 RTT,如下图

    client_key_exchange:合法性验证通过之后,客户端计算产生随机数字 Premaster,并用证书公钥加密,发送给服务器; 

    2、False start

            False Start 有抢跑的意思,意味着不按规则行事。TLS False Start 是指客户端在发送 Change Cipher Spec Finished 同时发送应用数据(如 HTTP 请求),服务端在 TLS 握手完成时直接返回应用数据(如 HTTP 响应)。这样,应用数据的发送实际上并未等到握手全部完成,故谓之抢跑。这个过程如下图所示:

            浏览器在发送ChangeCipherSpec,并没有等待服务器端的确认就立即发送了加密应用数据,省去了一个RT。 False Start 相当于客户端提前发送加密后的应用数据,不需要修改 TLS 协议,目前大部分浏览器默认都会启用。

    Nginx服务器开启False start支持:

    1. # server {
    2. # listen 443 ssl http2 default_server;
    3. # listen [::]:443 ssl http2 default_server;
    4. # server_name _;
    5. # root /usr/share/nginx/html;
    6. # ssl_certificate "/etc/pki/nginx/server.crt";
    7. # ssl_certificate_key "/etc/pki/nginx/private/server.key";
    8. # ssl_session_cache shared:SSL:1m;
    9. # ssl_session_timeout 10m;
    10. ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!aNULL:!MD5; # 设置启用算法
    11. ssl_prefer_server_ciphers on; # 开启False start支持:
    12. #
    13. # #......
    14. # }

    简而言之就是告诉Nginx在TLS握手时启用服务器算法优先,由服务器选择适配算法而不是客户端。

    3、升级到HTTP2

    HTTP/2相比廉颇老矣的 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化包括有: 

    • 二进制分帧
    • 首部压缩(Header Compression)
    • 多路复用
    • 对请求划分优先级
    • 服务器推送流(即Server Push技术)
    1. # server {
    2. # 升级到https后的web应用是基于http2协议进行传输
    3. listen 443 ssl http2; # 关键设置
    4. # listen [::]:443 ssl http2 default_server;
    5. # server_name _;
    6. # root /usr/share/nginx/html;
    7. #
    8. # ssl_certificate "/etc/pki/nginx/server.crt";
    9. # ssl_certificate_key "/etc/pki/nginx/private/server.key";
    10. # ssl_session_cache shared:SSL:1m;
    11. # ssl_session_timeout 10m;
    12. # ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!aNULL:!MD5;
    13. # ssl_prefer_server_ciphers on;
    14. #
    15. # #......
    16. # }

    4、会话恢复(Session Resumption)机制

            SSL/TLS中的session跟HTTP的session类似,都是用来保存客户端和服务端之间交互的一些记录,这里的SSL的session保存的是SSL的握手记录。 

            TLS有几个特征可以用来消除额外的来回,比如重用一个会话session,两个标准会话重用机制是 session ID (RFC 5246)session tickets (RFC 5077),使用其中一个技术,一个客户端可以重用之前创建的会话,这个会话是之前和服务器进行握手成功的,这样可以减少一次来回过程。

    Session ID重用 / Session Cache: 

            一次TLS握手的结果是建立一条对称加密的数据通道,这条数据通道相关的参数都可以在内存中保存的,所以服务端就可以针对这一套参数值生成一个ID,叫做Session ID,使用该ID就可以直接复原对称加密的通信通道。所以当客户端下一次请求(Client Hello)到达的时候,客户端如果携带了Session ID,服务端就可以根据这个Session ID找到对应的Secure context,从而复原信道了。 

            OpenSSL的Session Cache是不能够跨进程共享的,而Nginx是一个多进程的业务模型,如果每一个进程一份独立的Session Cache,就会导致同样一个SSL连接,上一次被一个Worker进程服务,下一次是被另外一个Worker进程服务的。使得整个OpenSSL在内存上造成极大的浪费,并且使得Cache的准确性大幅度下滑。

            所以Nginx就另外又实现了一套自己的Session Cache系统,这套系统是跨进程的,使用的是共享内存,用红黑树的方式组织的Session Cache,并且对Session ID的定义也重新进行了封装,最终形成了一个跨进程了SessionCache。

    Session ID共享复用Nginx可以通过ssl_session_cache设置: 

    1. server {
    2. # ......
    3. # ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
    4. # off: 使用会话缓存是严格被禁止的: nginx 明确告诉客户端会话不可以重复使用。
    5. # none: 使用会话缓存是温和地不允许的: nginx告诉客户端会话也许可以重用,但并不会真的在缓存中保存会话参数。
    6. # builtin: 内置于OpenSSL的缓存; 只被一个worker进程使用.缓存大小用会话作为单位被指明。
    7. # 如果缓存大小未被给出,它就等于 20480 个会话。使用内置缓存会引起内存碎片。
    8. # shared: 一个缓存被所有worker进程共享。缓存大小用bytes作为单位被指明。1MB可以存储大约4000个会话。
    9. # 每一个共享的缓存应该有一个唯一的名称。拥有相同名称的缓存可以被多个虚拟服务(virtual server)所使用。
    10. ssl_session_cache shared:SSL:10m;
    11. # ......
    12. }

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

    ssl_session_cache builtin:1000 shared:SSL:10m;

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

    ssl_session_cache shared:SSL:10m; 

    Session Identifier 机制的弊端:

    •  负载均衡中,多机之间往往没有同步 session 信息,如果客户端两次请求没有落在同一台机器上就无法找到匹配的信息;
    • 服务端存储 Session ID 对应的信息不好控制失效时间,太短起不到作用,太长又占用服务端大量资源。
      • ssl_session_timeout 5m;

    Session Ticket:

            Session Ticket可以解决这些问题,Session Ticket 是用只有服务端知道的安全密钥加密过的会话信息,最终保存在浏览器端。浏览器如果在 ClientHello 时带上了 Session Ticket,只要服务器能成功解密就可以完成快速握手。 

            由于Session Ticket的客户端存储的特性,使得服务器完全就不需要管SessionCache方案需要面对的资源管理问题,唯一的代价就是一个解密计算的开销。同时,天然的支持了分布式,但是前提是分布式的所有节点都需要使用同样的服务器私有密钥(Session Ticket Encryption Key (STEK)),这无疑严重增加了危险系数。所以很多公司在实现的时候都会隔一段时间统一更换STEK。

    1. # 开启Session Ticket,此处没有指定加密算法,openssl默认会生成随机数的key
    2. # 如果需要手动指定,配置:ssl_session_ticket_key encode_decode.key;
    3. # key文件可以由openssl命令生成,例如:openssl rand 80> ticket.key
    4. # 如果存在Nginx集群,多个集群应用使用同一份key文件,为保证安全性须每隔一定频率更换key
    5. ssl_session_tickets on; # 开启配置

            在TLS1.3中,Session Cache和Session Ticket都被完全的取消,取而代之的是PSK(Pre Shared Key)。这个PSK并不是说每个客户端都要和服务端提前共享一个密钥,而是与握手相同的首先使用非对称加密方法直接提前协商一个密钥出来(psk_dhe_ke ),或者直接从之前协商出来的密钥参数中得出一个密钥(psk_ke)。

    5、HSTS

    一个网站接受一个HTTP的请求,然后跳转到HTTPS,用户可能在开始跳转前,通过没有加密的方式和服务器对话,比如,用户输入http://lagou.com或者直接lagou.com,会存在两个弊端:

    • 存在中间人攻击潜在威胁,跳转过程可能被恶意网站利用来直接接触用户信息,而不是原来的加密信息。
    • 降低访问速度,301/302 跳转不仅需要一个 RTT,浏览器执行跳转也需要执行时间。

    解决方案:

            HSTS(HTTP Strict Transport Security,严格传输安全协议)国际互联网工程组织IETF正在推行一种新的Web安全协议。采用HSTS协议的网站将保证浏览器始终连接到该网站的HTTPS加密版本,不需要用户手动在URL地址栏中输入加密地址。HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接,而不是HTTP。

    实现原理:

            第一次通过HTTPS请求,服务器响应Strict-Transport-Security 头,以后尝试访问这个网站的请求都会自动把HTTP替换为HTTPS。当HSTS头设置的过期时间到了,后面通过HTTP的访问恢复到正常模式,不会再自动跳转到HTTPS。每次浏览器接收到Strict-Transport-Security头,它都会更新这个网站的过期时间,所以网站可以刷新这些信息,防止过期发生。Chrome、Firefox等浏览器里,当您尝试访问该域名下的内容时,会产生一个307 Internal Redirect(内部跳转),自动跳转到HTTPS请求。

    开启方法:

    1. server {
    2. listen 443 ssl http2;
    3. server_name lagou.com;
    4. # 1. max-age:单位:秒。 HSTS header 过期时间,一般设置为1年,即31536000秒。
    5. # 而每次Response Header都带上HSTS Header,则可不断刷新其过期时间。
    6. # 2. includeSubDomains:需要开启HSTS的域名/子域名。
    7. # 在接下来的一年(即31536000秒)中,浏览器只要向xxx或其子域名发送HTTP请求时,必须采用HTTPS来发起连接。比如,用户点击超链接或在地址栏输入 http://xxx/ ,浏览器应当自动将 http 转写成https,然后直接向https://xxx/ 发送请求。
    8. add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # 关键开启代码
    9. location / {
    10. }
    11. }

    避免中间人攻击:在公共场所,例如免费WIFI的环境中,第一次访问网站会弹出HSTS错误 

            当你通过一个无线路由器的免费 WiFi 访问你的网银时,很不幸的,这个免费WiFi 也许就是由黑客的笔记本所提供的,他们会劫持你的原始请求,并将其重定向到克隆的网银站点,然后,你的所有的隐私数据都曝光在黑客眼下。 

            严格传输安全可以解决这个问题。如果你之前使用 HTTPS 访问过你的网银,而且网银的站点支持 HSTS,那么你的浏览器就知道应该只使用 HTTPS,无论你是否输入了 HTTPS,这样就防范了中间人劫持攻击。

     

    6、OCSP stapling

    (1)PKI:公钥基础设施(Public-Key Infrastructure):

    公钥基础设施是用来实现基于公钥密码体制的密钥和证书的产生、管理、存储、分发和撤销等功能。

    (2)PKI的组成要素:

    订阅人

            订阅人(或者说最终实体)是指那些需要证书来提供安全服务的团体,拉勾教育。

    登记机构

            登记机构(registration authority,RA)主要是完成一些证书签发的相关管理工作。例如, RA会首先对用户进行必要的身份验证,然后才会去找CA签发证书。在某些情况下,当 CA希望在用户附近建立一个分支机构时(例如在不同的国家建立当地登记中心),我们也称RA为本地登记机构(localregistration authority,LRA)。实际上,很多CA也执行RA 的职责。

    证书颁发机构

            证书颁发机构(certification authority,CA)是指我们都信任的证书颁发机构,它会在确认申请用户的身份之后签发证书。同时CA会在线提供其所签发证书的最新吊销信息,这样信赖方就可以验证证书是否仍然有效。

    信赖方

            信赖方(relying party)是指那些证书使用者。技术上来说,一般是指那些执行证书验证的网页浏览器、其他程序以及操作系统。他们是通过维护根可信证书库来执行验证的, 这些证书库包含某些CA的最终可信证书(信任密钥,trust anchor)。更广泛地说,信赖方是指那些需要通过证书在互联网上进行安全通信的最终用户。

    (3)证书链:

    (4)浏览器证书检测:

            对于一个可信任的 CA 机构颁发的有效证书,在证书到期之前,只要 CA 没有把其吊销,那么这个证书就是有效可信任的。有时由于某些特殊原因(比如私钥泄漏,证书信息有误,CA 有漏洞被黑客利用,颁发了其他域名的证书等等),需要吊销某些证书。那浏览器或者客户端如何知道当前使用的证书已经被吊销了呢,通常有两种方式:

            1. CRL(Certificate Revocation List,证书吊销列表)CRL 是由 CA 机构维护的一个列表,列表中包含已经被吊销的证书序列号(证书的唯一标识)和吊销时间。浏览器可以定期去下载这个列表用于校验证书是否已被吊销。而且当一个证书刚被吊销后,浏览器在更新 CRL 之前还是会信任这个证书的,实时性较差。

    2. OCSP(Online Certificate Status Protocol,在线证书状态协议)具体实现是一个在线证书查询接口,它建立一个可实时响应的机制,让浏览器发送查询证书请求到CA服务器,然后CA服务器实时响应验证证书是否合法有效,这样可以实时查询每一张证书的有效性,解决了 CRL的实时性问题。 

            但是 OCSP 又有另外两个问题:CA服务器上的隐私和性能问题。由于OCSP要求浏览器直接请求第三方CA以确认证书的有效性,因此会损害隐私。CA知道什么网站访问了该CA以及哪些用户访问了该网站。而这些数据对于跨国业务网站或者政企网站尤为敏感。 

            另一方面,某些客户端会在 SSL 握手时去实时查询 OCSP 接口,并在获得查询结果前会阻塞后续流程,在网络不佳时(尤其是内陆地区)会造成较长时间的页面空白,降低了HTTPS性能,严重影响用户体验。在服务器上部署OCSP Stapling将可以解决以上问题。

    OCSP Stapling 就是为了解决 OCSP 性能问题而生的,工作原理: 

            网站服务器将自行查询OCSP服务器并缓存响应结果,然后在与浏览器进行TLS连接时将 OCSP 查询结果通过 Certificate Status 消息发送给浏览器,这样浏览器就不需要再去查询了。

            浏览器客户端也不再需要向任何第三方披露用户的浏览习惯,完美解决了隐私问题。当客户端向服务器发起 SSL 握手请求时,服务器将证书的 OCSP 信息随证书链一同发送给客户端,避免了客户端验证会产生的阻塞问题。由于 OCSP 响应是无法伪造的,因此这一过程也不会产生额外的安全问题。

    (5)Nginx启用OCSP stapling:

    1. # 开启 OCSP Stapling ---当客户端访问时 NginX 将去指定的证书中查找OCSP 服务的地址,获得响应内容后通过证书链下发给客户端。
    2. ssl_stapling on;
    3. # 启用OCSP响应验证,OCSP信息响应适用的证书
    4. ssl_stapling_verify on;
    5. # 指定完整的证书链
    6. ssl_trusted_certificate /path/chain.pem;
    7. # 根据Nginx文档,最好使用本地DNS服务,可以防止DNS欺骗(DNSspoofing)。使用公共的DNS服务,存在安全隐患。
    8. # 添加resolver解析OSCP响应服务器的主机名,valid表示缓存。(下面这些是公共的DNS解析地址)
    9. resolver 8.8.8.8 8.8.4.4 216.146.35.35 216.146.36.36 valid=6000s;
    10. # resolver_timeout表示网络超时时间
    11. resolver_timeout 5s;
  • 相关阅读:
    Springboot项目连接Redis(jedis)
    【Nginx】使用nginx进行反向代理与负载均衡
    TCP/IP 详解(第 2 版) 笔记 / 3 链路层 / 3.5 无线局域网 - IEEE 802.11 (Wi-Fi) / 3.5.1 802.11 帧
    Java——》线程的打断(停止正在运行的线程)
    Hadoop安装教程
    工业4.0 IO-Link RFID传感器|读写器应用前景与优势分析
    【P1008 [NOIP1998 普及组] 三连击】
    MySQL 事务
    Matlab:数值的显示格式
    【云原生】原来2020.0.X版本开始的OpenFeign底层不再使用Ribbon了
  • 原文地址:https://blog.csdn.net/weixin_52851967/article/details/125973126