HTTP 查漏补缺

TCP/IP 网络分层模型

从下向上划分。

  • 链接层(link layer)
    • 以太网、Wifi 底层网络发送原始数据包,工作在网卡层次,使用 MAC 标记网络设备
  • 网际层或者网络互联层(internet layer),IP 协议就处于这一层
    • IP 协议定义了 IP 地址,可以在链接层基础上,用 IP 地址取代 MAC 地址
  • 传输层(transport layer)
    • TCP、UDP 协议工作层次
  • 应用层(application layer)
    • HTTP、Telnet、SSH、FTP、SMTP 等

OSI 网络分层模型

开放式系统互通通信参考模型(Open System Interconnection Reference Model)。仅是一个参考,并不是强制标准。

从下向上划分。

  • 物理层,网络的物理形式
  • 数据链路层,相当于 TCP/IP 的链接层
  • 网络层,相当于 TCP/IP 的网际层
  • 传输层,相当于 TCP/IP 的传输层
  • 会话层,维护网络中连接状态,保持会话和同步
  • 表示层,把数据转换为合适、可理解的语法和语义
  • 应用层,面向具体的应用传输协议

五六七层统一对应 TCP/IP 的应用层。

HTTP

HTTP(HyperText Transfer Protocol) 是一个在计算机世界里用于专门在两点之间传输文本、图片、音频、视频等超文本数据的约定和规范。

HTTP 跑在 TCP/IP 协议栈之上,依靠 IP 协议实现寻址和路由、TCP 协议实现可靠数据传输、DNS 协议实现域名查找、SSL/TLS 协议实现安全通信。

特点:灵活可扩展、可靠的传输协议、应用层协议、使用请求-应答模式、无状态协议、明文传输、不安全

响应状态码

1xx:提示信息,目前是协议处理的状态,需要后续操作

  • 101 Switch Protocols,客户端使用 Upgrade 头字段,要求协议升级,比如 WebSocket 。

2xx:成功态,报文已经收到并被正确处理

  • 200 OK,常见成功状态码,响应头后通常存在 body 数据
  • 204 No Content,常见成功状态码,响应头通常不存在 body 数据
  • 206 Partial Content,HTTP 分块下载或断点续传的基础,客户端发送范围请求,服务端成功处理后,返回部分资源
    • 206 通常伴随头字段 Content-Range

3xx:重定向,资源位置发生变动,需要客户端发送请求

  • 301 永久重定向
  • 302 临时重定向
  • 304 Not Modified 表示资源未修改

4xx:客户端错误,请求报文错误,服务器无法处理

  • 400 Bad Request,通用的错误,表示请求报文错误
  • 403 Forbidden,服务端禁止访问资源
  • 404 Not Found,本意服务器无法提供资源,未找到资源
  • 405 Method Not Allowed,不允许使用某些方法操作资源
  • 408 Request Timeout,请求超时

5xx:服务端错误,服务器处理时内部发生错误

  • 500 Internal Server Error,通用错误码
  • 502 Bad Gateway,服务器网关错误或者代理错误
  • 503 Service Unavailable,服务器正忙,无法响应服务,503 是一个临时状态

HTTP 各版本差异

HTTP/0.9 1991 年发布的原型版本

  • 只支持纯文本内容
  • 仅支持 GET 命令

HTTP/1.0 1993 年发布

  • 传入的内容不再局限于文本,可以发送任何格式内容
  • 引入 HEAD、POST 等新方法
  • 引入 HTTP Header 概念,每次通信都必须包含头信息,用来描述一些元数据
  • 引入协议版本号概念
  • 增加响应状态码
  • 不支持断点续传

HTTP/1.1

  • 引入持久连接

    • TCP 默认不关闭,可以被多个请求复用,不需要显示声明 “Connection: keep-alive”
    • 长连接连接时长可以通过请求头中的 keep-alive 设置
  • 引入管线机制,同一个 TCP 连接里,客户端可以发送多个请求

  • 增加缓存管理和控制

    • 增加 E-Tag、If-Modified-Since、If-Match、If-None-Match 等字段
  • 支持断点续传

    • 使用请求头的 Range 来实现
  • 允许响应数据分块(chunked),利用传输大文件

  • 新增方法 PUT、DELETE、PATCH、OPTIONS 新方法

HTTP/2

  • 二进制分帧

    • 采用二进制格式,这样报文格式就被拆分为一个个乱序的二进制帧,用 Headers 帧存放头部字段,Data 帧存放请求体数据等,因为不需要排队等待,一定程度上解决了队头阻塞问题
  • 头部压缩

    • HTTP/1.1 版本会出现 User-Agent/Cookie/Accept/Server/Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,导致头部偏重,HTTP/2 使用 HPACK 算法进行压缩
  • 多路复用

    • 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,一定程度上解决了队头阻塞的问题
  • 服务器推送

    • 允许服务器未经请求,主动向客户端发送资源,即服务器推送
  • 请求优先级

    • 可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验

HTTP/3

  • QUIC 协议

    • 运行在 QUIC 之上的 HTTP 协议被称为 HTTP/3QUIC 协议基于 UDP 实现,同时也整合了 TCPTLSHTTP/2 的优点,并加以优化
  • 零 RTT 建立连接

    • 首次连接只需要 1 RTT,后面的连接更是只需 0 RTT,意味着客户端发给服务端的第一个包就带有请求数据
  • 连接迁移

    • QUIC 连接不以四元组(源 IP、源端口、目的 IP、目的端口)作为标识,而是使用一个 64 位的随机数,这个随机数被称为 Connection ID,即使 IP 或者端口发生变化,只要 Connection ID 没有变化,那么连接依然可以维持
  • 多路复用

    • QUIC 的传输单元是 Packet,加密单元也是 Packet,整个加密、传输、解密都基于 Packet,这样就能避免 TLS 的队头阻塞问题
    • QUIC 基于 UDPUDP 的数据包在接收端没有处理顺序,即使中间丢失一个包,也不会阻塞整条连接,其他的资源会被正常处理
  • 改进的拥塞控制

    • 热插拔,TCP 中如果要修改拥塞控制策略,需要在系统层面进行操作,QUIC 修改拥塞控制策略只需要在应用层操作,并且 QUIC 会根据不同的网络环境、用户来动态选择拥塞控制算法
    • 前向纠错 FEC,使用前向纠错(FECForward Error Correction)技术增加协议的容错性,一段数据被切分为 10 个包后,依次对每个包进行异或运算,运算结果会作为 FEC 包与数据包一起被传输,如果不幸在传输过程中有一个数据包丢失,那么就可以根据剩余 9 个包以及 FEC 包推算出丢失的那个包的数据
    • 单调递增的 Packet Number,与 TCPSequence Number 不同的是,Packet Number 严格单调递增,如果 Packet N 丢失了,那么重传时 Packet 的标识不会是 N,而是比 N 大的数字,比如 N + M,这样发送方接收到确认消息时就能方便地知道 ACK 对应的是原始请求还是重传请求
    • 更多的 ACK 块,QUIC 最多可以捎带 256ACK block,在丢包率比较严重的网络下,更多的 ACK block 可以减少重传量,提升网络效率
  • 流量控制

    • TCP 会对每个 TCP 连接进行流量控制,而 QUIC 只需要建立一条连接,在这条连接上同时传输多条 Stream

WebSocket

WebSocket 协议依赖于 HTTP。

WebSocket 是一个 “全双工” 的通讯协议,与 TCP 一样,客户端和服务端都可以随时向对方发送数据。

WebSocket 握手是一个标准的 HTTP Get 请求。

但是要带上两个协议升级的头字段:

  • Connection: Upgrade,表示要求协议升级
  • Upgrade: websocket,表示要升级成 WebSocker 协议

还增加了两个额外的认证头字段:

  • Sec-WebSocket-Key:一个 base64 编码的 16 字节随机数,作为简单的认证密钥
  • Sec-WebSocket-Version:协议版本号,当前必须是 13

服务端会返回特殊的 “101 Switching Protocols” 响应报文,接下来请求就用 HTTP,改用 WebSocket 协议进行通信。

HTTPS、SSL\TLS

https 主要用来解决 http 的缺点,明文传输和不安全。

如果通信过程具备机密性、完整性、身份认证和不可否认,就可以认为是安全的。

  • 机密性,数据保密,不能让不相关的人看到消息
  • 完整性,数据传输过程中不会被修改
  • 身份认证,可以确认对方身份,保证消息发送给可信的人
  • 不可否认,不能否认已经发生过的行为

https 在 http 的基础之上增加了上面所说的 3 大特性。

https 其实就是把 HTTP 下层的传输协议由 TCP/IP 协议换成 SSL/TLS,即 HTTP Over SSL/TLS。

SSL 即安全套接层(Secure Sockets Layer),位于 OSI 模型中的第 5 层。于网景公司 1994 年发明,有 v2、v3 两个版本。

1999 年互联网工程组 IETF 把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 开始,所以 TLS 1.0 实际就是 SSL v3.1。

目前应用广泛的 TLS 是 1.2。TLS 协议由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。

浏览器和服务器使用 TLS 建立连接时需要选择一组恰当的加密算法和实现安全通信,这些算法的组合被称为 “密码套件”(cipher suite,加密套件)。

TLS 密码套件命名非常规范,基础形式是 “密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法”。

以下面的密码套件为例:

ECDHE-RSA-AES256-GCM-SHA384

握手时使用 ECDHE 算法进行密钥交换,使用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数。

TLS 中使用混合加密方式,实现机密性。

  • 通信刚开始使用非对称加密算法,比如 RSA、ECDHE 解决密钥交换的问题。
  • 完成会话密钥安全交换之后,后续不再使用非对称加密,全部使用对称加密。

TLS 中使用摘要算法(Digest Algorithm)实现完整性。

  • 摘要算法是特殊的 “单向” 加密算法,它只有算法,没有密钥,加密后的数据无法解密。
  • 摘要算法保证 “数字摘要” 和原文是完全等价的。只要在原文后附上它的摘要,就能够保证数据的完整性。
  • 摘要算法不具备机密性,明文传输同样也存在问题,所以 TLS 在传输过程会使用会话密钥同时加密消息和摘要。

TLS 中使用数字签名同时实现身份认证和不可否认。

  • 私钥只能本人持有,其他任何人都不会拥有。
  • 使用私钥加上摘要算法,就可以实现 “数字签名”。私钥仅加密原文摘要,形成数字签名,使用公钥对其进行解析,获取摘要后,比对原文验证其完整性。
  • 上述两个行为存在两个专业术语,叫做 “签名” 和 “验签”。
  • 对于 “公钥信任” 问题,需要通过 CA(Certificate Authority,证书认证机构)构建起公钥信任链,保证公钥是安全可靠的。

数字证书和 CA

  • 公钥的分发需要使用数字证书,必须由 CA 的信任链来验证,否则就是不可信的。
  • CA 证书中包含要发给客户端的公钥、签发者、过期时间等信息。
  • 数字签名和数字证书仅用于 TSL/SSL 的握手阶段,用来保证服务器的公钥能够正确地传递给浏览器。

证书信任链过程:

以二级 CA 证书为例,服务器返回的是证书链(不包括根证书)。
浏览器获取到证书链,会根据证书链中的签发者信息,逐层向上查找到根证书,并从根证书开始逐级向下做验签。
首先使用信任的根证书(公钥)解析证书链的根证书得到一级证书的公钥和摘要验签,然后拿一级证书的公钥解密一级证书拿到二级证书的公钥和摘要验签,再然后拿二级证书的公钥解密二级证书得到服务器的公钥和摘要验签,验证过程结束。

验签过程:

首先证书包括四部分:

  • signedCertificate 签名的证书,即浏览器点击小锁头直观可以看到的证书
  • algorithmIdentifier 算法标记,包括了签名证书用到的摘要和签名算法
  • encrpted 加密摘要,加密摘要不包含在 signedCertificate 中,浏览器中点击小锁头看不到加密摘要

使用传递过来的摘要算法 algorithmIdentifier 对一级证书的 signedCertificate 做摘要。
使用根证书解密解析一级证书的数字签名 encrpted,得到发过来的摘要。
如果两者一致,则认为一级 CA 证书是真实有效的。

如果中间人截获证书,将证书替换为自己申请的证书,并且使客户端信任中间人的根证书,这样中间人就可以使用这个根证书来 “伪造” 证书,冒充原网站, fiddler 就是这么做的。简单修改证书是不行的,因为证书会被 CA 签名,可以防止被篡改,中间人得不到 CA 的私钥,就没办法伪造。

TLS 1.2 连接过程

TLS 协议包含几个子协议,也可以理解为几个不同职责的模块,常见的有记录协议、警报协议、握手协议、变更密码协议等。

  • 记录协议(Record Protocol)规定 TLS 收发收据的基本单位:记录(Record)。
    • 类似 TCP 里的 segment,所有的其他子协议都需要记录协议发出。
    • 多个记录数据可以在一个 TCP 包里一次性发出,不需要像 TCP 那样返回 ACK。
  • 警报协议(Alert Protocol)的职责是像对方发送警告信息
    • 类似 HTTP 协议里的状态码
    • protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题
  • 握手协议(Handshake Protocol)是 TLS 里最复杂的子协议
    • 比 TCP 的 SYN/ACK 复杂的多
    • 浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥
  • 变更密码规范协议(Change Cipher Spec Protocol)
    • 非常简单,就是一个 “通知”,告诉对方,后续数据都将使用加密保护

TLS 握手过程简要图。

tls.webp

ECDHE 握手过程

下图是 TLS 的握手过程。

ECDHE.webp

TCP 建立连接之后:

浏览器会发送 “Client Hello” 消息。包含客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于生成会话密钥。

Handshake Protocol: Client Hello
    Version: TLS 1.2 (0x0303)
    Random: 1cbf803321fd2623408dfe…
    Cipher Suites (17 suites)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

服务器收到 “Client Hello” 后,返回 “Server Hello” 消息,对比版本号,也会给出一个随机数(Server Random),然后从客户端的列表里选择一个本次通信要使用的密码套件。假定这里选择 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384。

Handshake Protocol: Server Hello
    Version: TLS 1.2 (0x0303)
    Random: 0e6320f21bae50842e96…
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

然后服务器为了证明自己的身份,还要把证书发送给客户端(Server Certificate)。

其次因为服务器选择 ECDHE 算法,所以会在发送证书后,发送 “Server Key Exchange” 消息,里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。

Handshake Protocol: Server Key Exchange
    EC Diffie-Hellman Server Params
        Curve Type: named_curve (0x03)
        Named Curve: x25519 (0x001d)
        Pubkey: 3b39deaf00217894e...
        Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
        Signature: 37141adac38ea4...

最后服务器发送 “Server Hello Done” 消息。

这样第一个消息往返就结束了,共发送两个 TCP 包,客户端和服务端通过明文共享了三个信息,Client Random、Server Random 和 ServeParams。

客户端此时已经拿到服务器的根证书,会开始走证书链验证,确认证书的真实性,再用证书公钥验证签名,就可以确认服务器身份。可以继续往下执行。

客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params),发送 “Client Key Exchange” 消息给服务器。

Handshake Protocol: Client Key Exchange
    EC Diffie-Hellman Client Params
        Pubkey: 8c674d0e08dc27b5eaa…

此时客户端和服务器都拿到了密钥交换算法的两个参数(Client Params、Server Params),然后使用 ECDHE 算法计算得到 “Pre-Master”,也是一个随机数。

现在客户端和服务器手里就存在了三个随机数:Client Random、Server Random、Pre-Master。用这三个数可以生成用于加密会话的主密钥,即 “Master-Secret”。

master_secret = PRF(pre_master_secret, “master secret”, ClientHello.random + ServerHello.random)

RPF 是伪随机数函数,基于密码套件中的最后一个参数,比如本次密码套件中的 SHA384,通过摘要算法再一次强化 “Master Secret” 的随机性。

主密钥有 48 字节,它也不是最终用于通信的会话密钥,还会用 PRF 扩展出更多的密钥,比如客户端发送的会话密钥(client_write_key)、服务器发送的会话密钥(server_write_key)等,避免只使用一个密码带来的安全隐患。

有了主密钥和派生的会话密钥,握手就快结束了。

客户端发送 “Change Cipher Spec”,然后再发送 “Finished” 消息,把之前发送的数据做一个摘要,加密一下让服务器做验证。

服务器也是同样的操作,发送 “Change Cipher Spec” 和 “Finished” 消息,双方都验证加密解析 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。

“Client Params 和 Server Params 都可以被截获,为何中间人没法通过这两个信息计算 Pre-Master Secret 呢?

  • 客户端随机生成随机值 Ra,计算 Pa(x, y) = Ra * Q(x, y),Q(x, y) 为全世界公认的某个椭圆曲线算法的基点。将 Pa(x, y) 发送至服务器。
  • 服务器随机生成随机值 Rb,计算 Pb(x,y) = Rb * Q(x, y)。将 Pb(x, y) 发送至客户端。
  • 客户端计算 Sa(x, y) = Ra * Pb(x, y),服务器计算 Sb(x, y) = Rb * Pa(x, y)
  • 算法保证了Sa = Sb = S,提取其中的 S 的 x 向量作为密钥(预主密钥)

https://blog.csdn.net/mrpre/article/details/78025940

RSA 握手过程

刚才说的 ECDHE 握手过程,是目前主流的 TLS 握手过程,这与传统的握手有两点不同。

  • 使用 ECDHE 实现密钥交换,而不是 RSA,所以在服务端会发出 “Server Key Exchange” 消息。
  • 因为使用 ECDHE,客户端可以不用等到服务器返回 “Finished” 确认握手完毕,立即就发出 HTTP 报文,省去一个消息往返的时间。不等连接完全建立就提前发送应用数据,提高传输的效率。

rsa.webp

双向认证

上面所说的只是 “单向认证” 握手过程,只认证了服务器身份,并没有认证客户端身份。

这是因为通常单向认证后就已经建立了安全通信,用账号、密码等简单的手段就可以确认用户的真实身份。

为了防止账号、密码被盗,例如网上银行还会使用 U 盾给用户颁发客户端的证书,实现 “双向认证”,这样会更加安全。双向认证的流程也没有太多变化,只是在 “Server Hello Done” 之后,“Client Key Exchange” 之前,客户端端要发送 “Client Certificate” 消息,服务器收到后也要把证书链走一遍,验证客户端身份。

TLS 1.3 特性分析

TLS 1.3 与 2018 年发布,它有三个主要改进目标:兼容、安全与性能。

兼容性

TLS 1.3 在保持 TLS 1.2 现有的记录格式不变的前提下,通过 “伪装” 实现兼容。

TLS 1.3 使用一个新的扩展协议(Extension Protocol),通过在记录末尾添加一系列的 “扩展字段” 来增加新的功能,并实现 “向后兼容”。

记录头的 Version 字段被固定的前提下,TLS 1.3 协议握手的 “Hello” 消息后面都会有 “supported_versions” 扩展,它用于标记 TLS 版本号,使用它就可以区分新旧协议。

Handshake Protocol: Client Hello
    Version: TLS 1.2 (0x0303)
    Extension: supported_versions (len=11)
        Supported Version: TLS 1.3 (0x0304)
        Supported Version: TLS 1.2 (0x0303)

TLS 1.3 利用扩展实现了许多重要功能,比如 “supported_groups”、“key_share”、“signature_algorthms”、“server_name” 等。

强化安全

TLS 1.2 也有很多漏洞和加密算法的弱点,TLS 1.3 在协议里修补了这些不安全因素。

  • 伪随机数函数由 PRF 升级为 HKDF(HMAC-based Extract-and-Expand Key Derivation Function);
  • 明确禁止在记录协议里使用压缩;
  • 废除了 RC4、DES 对称加密算法;
  • 废除了 ECB、CBC 等传统分组模式;
  • 废除了 MD5、SHA1、SHA-224 摘要算法;
  • 废除了 RSA、DH 密钥交换算法和许多命名曲线。

TLS 1.3 只保留了 AES、ChaCha20 对称加密算法,分组模式只能用 AEAD 的 GCM、CCM 和 Poly1305,摘要算法只能用 SHA256、SHA384,密钥交换算法只有 ECDHE 和 DHE,椭圆曲线只剩下 P-256 和 x25519 等 5 种。

废除 RSA 和 DH 密钥交换算法的原因:

RSA 不具备 “前向安全”(Forward Secrecy)。如果一个黑客一直在长期收集混合加密系统收发的所有报文,一旦私钥泄露或被破解,那么黑客就能够使用私钥解密出之前所有报文的 “Pre-Master”,再算出会话密钥,破解所有密文。

ECDHE 算法会在每次握手时都生成一对临时的公钥和私钥,每次通信的密钥对都是不同的,即使黑客破解这一次的会话密钥,也只是这次通信被攻击,历史消息不会受到影响,仍然是安全的。

提升性能

HTTPS 建立连接时不仅要做 TCP 握手,还要做 TLS 握手,在 TLS 1.2 中会多花两个消息往返时间(2-RTT),可能会导致几十毫秒甚至上百毫秒的延迟,在移动网络中延迟还会更加严重。

TLS 1.3 压缩了以前的 “Hello” 协商过程,删除 “Key Exchange” 消息,把握手时间减少到 “1-RTT”,效率提升一倍。客户端在 “Client Hello” 消息里直接用 “suppported_groups” 带上支持的曲线,比如 P-256、X25519,用 “key_share” 带上曲线对应的客户端公钥参数,用 “signuture_algorithms” 带上签名算法。

服务器收到后会在这些扩展里选定一个曲线和参数,再用 “key_share” 扩展返回服务器这边的公钥参数,就实现了双方的密钥交换。

tls1.3.webp

TLS 1.3 握手分析

tls1.3_02.webp

TCP 建立连接后,浏览器首先还是会发一个 “Client Hello”。

因为 1.3 的消息兼容 1.2,所以开头的版本号、支持的密码套件和随机数(Client Random)结构都是一样的(随机数是 32 个字节)。

Handshake Protocol: Client Hello
    Version: TLS 1.2 (0x0303)
    Random: cebeb6c05403654d66c2329…
    Cipher Suites (18 suites)
        Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
        Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
        Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
    Extension: supported_versions (len=9)
        Supported Version: TLS 1.3 (0x0304)
        Supported Version: TLS 1.2 (0x0303)
    Extension: supported_groups (len=14)
        Supported Groups (6 groups)
            Supported Group: x25519 (0x001d)
            Supported Group: secp256r1 (0x0017)
    Extension: key_share (len=107)
        Key Share extension
            Client Key Share Length: 105
            Key Share Entry: Group: x25519
            Key Share Entry: Group: secp256r1

“supported_versions” 表示这是 TLS 1.3,“supported_groups” 是支持的曲线,“key_share” 是曲线对应的参数。

服务器收到 “Client Hello” 消息后,会返回 “Server Hello” 消息,还要给出一个随机数(Server Random)和选定的密码套件。

Handshake Protocol: Server Hello
    Version: TLS 1.2 (0x0303)
    Random: 12d2bce6568b063d3dee2…
    Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
    Extension: supported_versions (len=2)
        Supported Version: TLS 1.3 (0x0304)
    Extension: key_share (len=36)
        Key Share extension
            Key Share Entry: Group: x25519, Key Exchange length: 32

“supported_versions” 确认使用 TLS 1.3,“key_share” 扩展中带上曲线和对应的公钥参数。

这时只交换了两条消息,客户端和服务端就拿到四个共享信息:Client Random 和 Server Random、Client Params 和 Server Params,两边就可以各自利用 ECDHE 算出 “Pre-Master”,再用 HKDF 生成主密钥 “Master Secret”,效率比 TLS 1.2 提高很多。

算出主密钥后,服务器立刻发出 “Change Cipher Spec” 消息,比 TLS 1.2 提早进入加密通信,后面的证书等就是加密的,减少了握手时的明文信息泄露。

这里 TLS 1.3 还有一个安全强化措施,多了个 “Certificate Verify” 消息,用服务器的私钥把前面的曲线、套件、参数等数据加了签名,作用和 “Finished” 消息差不多。但因为是私钥签名,所以强化了身份认证和防篡改。

这两个 “Hello” 消息之后,客户端验证服务器证书,再发 “Finished” 消息,就正式完成握手,开发收发 HTTP 报文。

HTTPS 优化

HTTP 连接大致上可以划分为两部分,一部分是建立连接时的非对称加密握手,另一部分是握手后的对称加密报文传输。

因为目前的 AES、ChaCha 20 性能都很好,还有硬件优化,报文传输的性能损耗并不大。所以,通常所说的 “HTTPS” 连接慢指的是刚开始建立连接的那段时间。

TCP 建立连接之后,正式数据传输之前,HTTPS 比 HTTP 增加了 TLS 握手的步骤,这个步骤最长可以花费 2-RTT。并且在握手消息的网络耗时之外,还有其他的一些 “隐性” 消耗:

  • 用于密钥交换的临时公私钥对(ECDHE);
  • 验证证书时访问 CA 获取 CRL 或者 OSCP;
  • 非对称加密解密处理 “Pre-Master”。

最差情况下,HTTPS 建立连接可能会比 HTTP 慢几百毫秒甚至几秒,这其中既有网络耗时,也有计算耗时。不过目前已经有很多行之有效的HTTPS 优化手段,运用得好可以把连接的额外耗时降低到几十毫秒甚至是 “零”。

下面这张图中已经把 TLS 握手过程中影响性能的部分都标记出来。

tls_optimize.webp

硬件优化

首先,可以选择更快的 CPU,最好可以内建 AES 优化,这样既可以加速握手,也可以加速传输。

其次,可以选择 “SSL 加速卡”,加解密时调用它的 API,让专门硬件来做非对称加解密,分担 CPU 计算压力。

SSL 加速卡缺点:升级慢、支持算法有限,不能灵活定制解决方案等

最后,我们还可以选择 “SSL 加速服务器”。用专门的服务器集群来彻底 “卸载” TLS 握手时的加解密计算。

软件升级

软件升级即把现在正在使用的软件尽量升级到最新版本。例如 Linux 内核、Nginx 版本、OpenSSL 版本。

协议优化

尽可能使用 TLS 1.3,它大幅度简化了握手进程,完全握手只需要 1-RTT,而且更安全。

如果不能升级 1.3,只能使用 1.2,握手使用的密钥交换尽量选用椭圆曲线的 ECDHE 算法。

ECDHE 算法运算速度快,安全性高,还支持 “False Start”,能够把握手的消息往返减少到 1-RTT。

椭圆曲线最好选择是 x25519,其次是 P-256。对称加密算法,可以选用 “AES_128_GCM”。

Nginx 里可以使用 “ssl_ciphers”、“ssl_ecdh_curve” 等指令配置服务器使用的密码套件和椭圆曲线优先级。


ssl_ciphers     TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:EECDH+CHACHA20;
ssl_ecdh_curve  X25519:P-256;
nginx

证书优化

除了密钥交换,握手过程中的证书验证也是一个比较耗时的操作。这里有两个优化点,一个是证书传输、一个是证书验证。

服务器证书可以选择椭圆曲线(ECDSA)证书而不是 RSA 证书。

224 位的 ECC 比 2048 位的 RSA 量级小很多,既能够节约带宽也能够减少客户端运算量。

使用 OCSP(在线证书状态协议,Online Certificate Status Protocol),向 CA 发送查询请求,返回证书有效状态。

还可以安装 “OCSP Sttaping” 补丁,它可以让服务器预先访问 CA 获取 OCSP 响应,然后在握手时随证书一起发给客户端,免去客户端连接 CA 的查询时间。

Nginx 里可以用指令 “ssl_stapling on” 开启 “OCSP Stapling”。

会话复用

HTTPS 连接的过程中,需要计算主密钥 “Master Secret”,主密钥每次连接时都要重新计算,将主密钥缓存重用,就可以避免握手和计算成本。这种做法就是 “会话复用”(TLS session resumption)。

会话复用分为两种,分别是 “Session ID” 和 “Session Ticket”。

“Session ID” 就是客户端和服务器首次连接后各自保存一个会话的 ID 号,内存里存储主密钥和其他相关的信息。当客户端再次连接时发一个 ID 过来,服务器就在内存里找,找到就直接用主密钥恢复会话状态,跳过证书验证和密钥交换过程,只用一个消息往返就可以建立安全通信。

“Session ID” 是最早出现的会话复用技术,应用最广,但它也有缺点,服务器必须保存每一个客户端的会话数据,对于拥有百万、千万级别用户的网站来说需要存储大量数据,会加重服务器负担。

“Session Ticket” 类似于 HTTP 的 Cookie,就是将存储的责任由服务器转移到客户端,服务器加密会话信息,用 “New Session Ticket” 消息发送给客户端,让客户端保存。

重连的时候,客户端使用扩展 “session_ticket” 发送 “Ticket” 而不是 “Session ID”,服务器解密后验证有效期,就可以恢复会话,开始加密通信。

“Session Ticket” 需要使用一个固定的密钥文件(ticket_key)来加密 Ticket,为了防止密钥被破解,保证 “前向安全”,密钥文件需要定期轮换,比如设置一小时或者一天。

预共享密钥

“False Start”、“Session ID”、“Session Ticket” 等方式只能实现 1-RTT。TLS 1.3 中进一步实现了 “0-RTT”,原理和 “Session Ticket” 类似,但在发送 Ticket 时会带上应用数据(Early Data),免去 1.2 里的服务器确认步骤,这种方式称为 “Pre-shared Key”,简称 “PSK”。

“PSK” 并不是完美的,它为了追求效率牺牲了安全性,容易受到 “重放攻击”(Replay attack)威胁。

单点登录(SSO)

https://mp.weixin.qq.com/s/6d_16hfd5Fz8pZvdpO3BEw

相对于单系统登录, SSO 需要一个认证中心,只有认证中心接受用户的用户名密码等安全信息,其他系统不再提供登录入口,只接受认证中心的间接授权。

间接授权通过令牌实现,SSO 认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统呢到令牌,得到授权之后,可以借此创建局部会话,局部会话登录方式与单系统登录方式相同。这个过程就是单点登录系统的原理。

sso.jpg