HTTP协议探究(四):TCP和TLS优化

时间:2021-09-14 06:06:39

一 复习与目标

1 复习

  • 简单密码学、对称加密与非对称加密
  • 数字签名、数字证书
  • SSL/TLS
  • HTTPS = HTTP + SSL/TLS,SSL/TLS为HTTP提供了保密性、完整性和鉴别性

2 目标

  • TCP性能调优
  • TLS性能调优

注:调优提前说明是担心讲完H2、WebSocket协议,导致前面的协议忘的差不多了,所以提前学习,提前使用上(偏重实战)。

二 TCP性能调优

1 性能检查清单

  • 服务器内核升级到最新版本(Linux:3.7 +)
  • 确保启动窗口缩放;
  • 确保cwnd大小为 10:
  • 禁用空闲后的慢启动;
  • 减少传输冗余数据;
  • 压缩要传输的数据;
  • 把服务器放到离用户近的地方以减少往返时间(CDN);
  • 尽最大可能重用已经建立的 TCP 连接:启动TFO。

2 上述优化详解

(1)服务器内核升级

  • TCP的最佳实践以及影响其性能的底层算法一直在与时俱进,但是大多数变化都只在最新内核中才有实现。

  • 希望使用TCP的最佳实践和优化算法,则需要升级内核。

(2)启动窗口缩放:RFC 1323

  • TCP的Window Size(rwnd,Receiver Window)起初只有65535字节。
  • 1992年RFC 1323添加Window Scale选项,将其提升到了1G字节。
  • Window Size太小时,在带宽延迟积很高的网络中,TCP无法获取最优性能。
# 检查是否开启,一般默认开启
$> sysctl net.ipv4.tcp_window_scaling
# 开启指令
$> sysctl -w net.ipv4.tcp_window_scaling=1

(3)增大初始拥塞窗口:RFC 6928

  • initcwnd起初默认为1,1999年RFC 2581将其增加到4,2013年RFC 6928提高到10。
  • 当cwnd=10,一次RTT就能发送10个MSS大小的TCP报文。即相比cwnd=1,发送速度翻了10倍。

注:cwnd用于控制发送速度(一次RTT能发送多少个报文段),MSS用于控制单个报文段的大小。

(4)慢启动重启(Slow-Start Restart)

  • 连接空闲一定时间后重置连接的拥塞窗口,即设置cwnd为初始值。
# 查看
$> sysctl net.ipv4.tcp_slow_start_after_idle
# 禁用
$> sysctl -w net.ipv4.tcp_slow_start_after_idle=0

(5)TCP快速打开(Tcp Fast Open): RFC 7413

  • 在某些条件下,允许在第一个SYN 分组中发送应用程序数据。TFO是一种新的优化选项,需要客户端和服务器共同支持。
# 查看
$> sysctl net.ipv4.tcp_fastopen
# 修改
$> sysctl -w net.ipv4.tcp_fastopen=3 # nginx 编译时添加
--with-cc-opt=-DTCP_FASTOPEN=23 # nginx.conf配置
listen 443 ssl http2 fastopen=3 reuseport default_server;

注:yum或者apt-get可以使用重新编译替换来升级nginx

  • 下载nginx源码
  • 编译 ./configure 添加需要的模块或者配置
  • make
  • 复制objs目录下的nginx替换/usr/sbin/nginx

3 知识点补充

(1)Linux下查看socket统计信息:ss

# 显示所有连接
$ ss | less
# TCP连接过滤出来
$ ss -at
# 打印进程名和进程号
$ ss -ltp
# 打印统计概要
$ ss -s
# 显示目的端口是443或80的套接字
$ ss -nt '( dst :443 or dst :80 )'
# 具体请自行查询资料

(2)带宽延迟积

  • BDP(Bandwidth-delay product,带宽延迟积):数据链路的容量与其端到端延迟乘积。这个结果就是任意时刻处于在途未确认状态的最大数据量。
  • 带宽延迟积取决于拥塞窗口(cwnd)和接收窗口(rwnd)的最小值,带宽延迟积(1次RTT内) = Math.min(rwnd * window scale,cwnd * MSS)
  • 实验1:带宽不重要!
    • 假设MSS = 1460,cwnd = 10,那么一次RTT内的最大数据量为1460 * 10 约16KB,RTT为100ms
    • 带宽 = 16 KB / 0.1 s= 1 310 720 bit/s = 1.31 Mbit/s
    • 即当前状态即使增加带宽,也无法使得TCP数据交换速度提升。
  • 实验2:请开启窗口缩放!
    • 不开启窗口缩放时,rwnd最大值为64KB
    • 假设现在RTT为100ms,网络可用带宽为10Mbit/s,理论带宽延迟积为100ms * 10Mbit/s = 122.1KB
    • 但是实际带宽延迟积最大值为64KB,远远小于122.1KB,所以需要开启窗口缩放来提升rwnd的大小。

(3)队首阻塞(HOL,Head of Line)

  • TCP 分组都会带着一个唯一的序列号被发出,而所有分组必须按顺序传送到接收端。
  • 如果中途有一个分组没能到达接收端,那么后续分组必须保存在接收端的TCP 缓冲区,等待丢失的分组重发并到达接收端。
  • 这一切都发生在TCP 层,应用程序对TCP 重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。
  • 在此之前,应用程序只能在通过套接字读数据时感觉到延迟交付。这种效应称为TCP 的队首阻塞

三 SSL/TLS性能调优

1 性能检查清单

  • 要最大限制提升 TCP 性能,参考第二章;
  • 把 TLS 库升级到最新版本,在此基础上构建(或重新构建)服务器;
  • 复用Session:会话缓存和无状态恢复;
  • 在接近用户的地方完成 TLS 会话,尽量减少往返延迟(CDN);
  • 配置 TLS 记录大小,使其恰好能封装在一个TCP段内:超出一个段将分段发送,效率变低,使用对的算法使TLS记录又短又安全;
  • 确保证书链不会超过拥塞窗口的大小,从信任链中去掉不必要的证书,减少链条层次;
  • 禁用服务器的 TLS 压缩功能;
  • 启用服务器对 SNI 的支持;
  • 启用服务器的 OCSP 封套功能;
  • 追加 HTTP 严格传输安全首部。

2 上述优化详解

(1)升级TLS库

  • TLS1.3能提供更快的访问速度,更强的安全性
# 升级openssl 1.1.0

# 查看版本
openssl version -a
# 下载编译安装 https://blog.csdn.net/kadwf123/article/details/80809004 # nginx升级到1.13并配置开启即可

(2)复用Session

  • 概述:复用Session能够实现简化握手

    • 减少了CPU消耗,因为不需要进行非对称密钥交换的计算。
    • 提升访问速度,不需要进行完全握手阶段二,节省了一个 RTT 和计算耗时。
  • Session Cache(会话缓存)

    • 概述:使用 client hello 中的 session Identifier查询服务端的 session cache, 如果服务端有对应的缓存,则直接使用已有的 session 信息(加密套件和密钥)提前完成握手,称为简化握手。
    • 缺点:
      • 消耗服务端内存来存储 session 内容
      • nginx,apache 只支持单机多进程间共享缓存,不支持多机间分布式缓存
    • 优点:
      • session id 是 TLS 协议的标准字段,市面上的浏览器全部都支持 session cache
    • 开启:
    # nginx开启
    # builtin a cache built in OpenSSL; used by one worker process only
    # shared a cache shared between all worker processes
    ssl_session_cache builtin:1000 shared:SSL:10m;
  • Session Ticket(无状态恢复)

    • 概述:server 将 session 信息加密成 ticket 发送给浏览器,浏览器后续握手请求时会发送 ticket,server 端如果能成功解密和处理 ticket,就能完成简化握手。类似java servlet中session与token。
    • 缺点:
      • session ticket 只是 TLS 协议的一个扩展特性,目前的支持率不是很广泛,只有 60% 左右。
      • session ticket 需要维护一个全局的 key 来加解密,需要考虑 KEY 的安全性和部署效率。
    • 开启:
    # 开启session ticket
    # 具体设置请参考nginx官网
    ssl_session_tickets on;
    ssl_session_timeout 60m;
    • 抓包
    Handshake Protocol: New Session Ticket
    Handshake Type: New Session Ticket (4)
    Length: 198
    TLS Session Ticket
    Session Ticket Lifetime Hint: 3600
    Session Ticket Length: 192
    Session Ticket: e3ed09ee89745302e35a2d829ac825504460b4d7e7f94c9a...
  • 分布式情况

    • Seesion绑定:把相同的客户端 IP 或相同的 TLS 会话 ID 路由到同一台服务器可以最好地利用会话缓存;
    • 缓存共享:在不适宜使用“单一”负载均衡策略的情况下,应该为多台服务器配置共享缓存,以便最好地利用会话缓存;

(3)证书链优化

  • 验证信任链需要浏览器遍历链条中的每个节点,从站点证书开始递归验证父证书,直至信任的根证书。

  • 如果证书链长度超过了TCP 的初始拥塞窗口,无意间就会让握手多了一次往返:服务器停下来等待客户端的ACK 消息。

  • 尽量减少中间证书颁发机构的数量 。

  • 很多站点会在证书链中包含根证书颁发机构的证书,这是完全没有必要的。(浏览器的信任名单中可能没有该根证书)

  • 理想的证书链应该在2 KB 或3 KB 左右,同时还能给浏览器提供所有必要的信息,避免不必要的往返或者对证书本身额外的请求。

(4)禁用TLS 压缩

  • TLS 内置的小功能,就是支持对记录协议传输的数据进行无损压缩。
  • 缺点:
    • “CRIME”攻击会利用 TLS 压缩恢复加密认证 cookie,让攻击者实施会话劫持
    • 传输级的 TLS 压缩不关心内容,可能会再次压缩已经压缩过的数据(图像),双重压缩会浪费服务器和客户端的CPU 时间
    • 大多数浏览器会禁用TLS 压缩

注:抱歉这个也没找到如何禁用...

(5)服务器名称指示(SNI)

  • 如何希望一个公有IP地址(同一端口:443)中使用多个虚拟主机(多个域名)就需要使用SNI。
  • TLS 协议的拓展,允许客户端在握手之初就指明要连接的主机名。Web 服务器可以检查SNI 主机名,选择适当的证书,继续完成握手。

注:具体参考:https://blog.csdn.net/xifeijian/article/details/56012586

(6)应用层协议协商(NPN和ALPN)

  • TLS 层的扩展,用于协商应用层使用的协议。它的前身是 NPN,最初用于支持 Google SPDY 协议(现已标准化为 HTTP/2)。 TLS 客户端和服务器版本的问题,导致 SPDY->HTTP/2 和 NPN -> ALPN 的切换。

  • NPN 是服务端发送所支持的HTTP协议列表,由客户端选择(Server Hello);而 ALPN 是客户端发送所支持的 HTTP 协议列表,由服务端选择(Client Hello);

  • NPN 的协商结果是在Change Cipher Spec之后加密发送给服务端;而ALPN的协商结果是通过Server Hello明文发给客户端;

  • NPN

# Server Hello截取报文
Extension: next_protocol_negotiation (len=12)
Type: next_protocol_negotiation (13172)
Length: 12
Next Protocol Negotiation
Protocol string length: 2
Next Protocol: h2
Protocol string length: 8
Next Protocol: http/1.1
  • ALPN
# Client Hello截取报文
Extension: application_layer_protocol_negotiation (len=14)
Type: application_layer_protocol_negotiation (16)
Length: 14
ALPN Extension Length: 12
ALPN Protocol
ALPN string length: 2
ALPN Next Protocol: h2
ALPN string length: 8
ALPN Next Protocol: http/1.1 # Server Hello截取报文
Extension: application_layer_protocol_negotiation (len=5)
Type: application_layer_protocol_negotiation (16)
Length: 5
ALPN Extension Length: 3
ALPN Protocol
ALPN string length: 2
ALPN Next Protocol: h2

(7)OCSP stapling

  • CRL(Certificate Revocation List,证书撤销名单)是RFC 5280 规定的一种检查所有证书状态的简单机制:每个证书颁发机构维护并定期发布已撤销证书的序列号名单。
  • OCSP(Online Certificate Status Protocol,在线证书状态协议),提供了一种实时检查证书状态的机制。
  • 与CRL包含被撤销证书的序列号不同,OCSP 支持验证端直接查询证书数据库中的序列号,从而验证证书链是否有效。总之,OCSP 占用带宽更少,支持实时验证。
  • OCSP要求CA站点必须处理实时查询,CA站点可能网络不稳定,RTT比较大。于是OCSP stapling实现不直接向 CA 站点请求 OCSP 内容。
  • 原理:浏览器发起 client hello 时会携带一个 certificate status request 的扩展,服务端看到这个扩展后将 OCSP内容直接返回给浏览器,完成证书状态检查。
# Nginx开启OCSP stapling
ssl_stapling on;
# 具体参考:https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html # 抓包
Certificate Status
Certificate Status Length: 471
OCSP Response
responseStatus: successful (0)
responseBytes
ResponseType Id: 1.3.6.1.5.5.7.48.1.1 (id-pkix-ocsp-basic)
BasicOCSPResponse
signature: 560a859a0126b0dc08e3f9a7431056bdf10de99e4e6a6d34...
......

(8)HTTP严格传输安全(HSTS)

  • HTTP 严格传输安全(HSTS,Strict Transport Security)是一种安全策略机制,它能让服务器通过简单的HTTP 首部(如Strict-Transport-Security: max-age=31536000)上述对适用的浏览器声明访问规则。
  • 所有对原始服务器的请求都通过 HTTPS 发送;
  • 所有不安全的链接和客户端请求在发送之前都应该在客户端自动转换为 HTTPS;
  • 万一证书有•错误,则显示错误消息,用户不能回避警告;
  • max-age 以秒为单位指定 HSTS 规则集的生存时间(例如,max-age=31536000 等于缓存365 天);
  • 用户代理可以根据指令在指定的证书链中记住某主机的指纹,以便将来访问时使用,从而有效限制证书颁发机构在特定时间(由max-age 指定)内可颁发证书的范围。(可选的)。
# Nginx 配置
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

(9)加密套件选择

  • 非对称密钥交换算法。建议优先使用 ECDHE,禁用 DHE,次优先选择 RSA。

  • 证书签名算法。由于部分浏览器及操作系统不支持 ECDSA 签名,目前默认都是使用 RSA 签名,其中 SHA1 签名已经不再安全,chrome 及微软 2016 年开始不再支持 SHA1 签名的证书 。

  • 对称加解密算法。优先使用 AES-GCM 算法,针对 1.0 以上协议禁用 RC4( rfc7465)。

  • 内容一致性校验算法。Md5 和 sha1 都已经不安全,建议使用 sha2 以上的安全哈希函数。

参考: