HTTPS 的 7 次握手以及 9 倍時延

HTTP 協議(Hypertext Transfer Protocol)已經成為互聯網上最常用的應用層協議,然而其本身只是用於傳輸超文本的網絡協議,不會提供任何安全上的保證,使用明文在互聯網上傳輸數據包使得竊聽和中間人攻擊成為可能,通過 HTTP 傳輸密碼其實與在互聯網上裸奔也差不多。

HTTPS 的 7 次握手以及 9 倍時延

https-banner

圖 1 - HTTPS 協議

網景(Netscape)在 1994 年設計了 HTTPS 協議,使用安全套接字層(Secure Sockets Layer,SSL)保證數據傳輸的安全[^1],隨著傳輸層安全協議(Transport Layer Security,TLS)的發展,目前我們已經使用 TLS 取代了廢棄的 SSL 協議,不過仍然使用 SSL 證書一詞[^2]。

HTTPS 是對 HTTP 協議的擴展,我們可以使用它在互聯網上安全地傳輸數據[^3],然而 HTTPS 請求的發起方第一次從接收方獲取響應需要經過 4.5 倍的往返延遲(Round-Trip Time,RTT)。本文將詳細介紹請求發起和響應的過程,分析為什麼 HTTPS 協議需要通過 4.5-RTT 的時間獲得服務提供方的響應:

  • TCP 協議 — 通信雙方通過三次握手建立 TCP 連接[^4];
  • TLS 協議 — 通信雙方通過四次握手建立 TLS 連接[^5];
  • HTTP 協議 — 客戶端向服務端發送請求,服務端發回響應;

這裡的分析建立在特定版本的協議實現以及常見場景上,隨著網絡技術的發展,我們能夠減少需要的網絡通信次數,本文會在對應章節中提到一些常見的優化方案。

TCP

HTTP 協議作為應用層協議,它需要底層的傳輸層協議為其提供基本的數據傳輸功能,HTTP 協議一般都會使用 TCP 協議作為底層協議。為了阻止錯誤的建立歷史連接,TCP 協議通信的雙方會通過三次握手建立 TCP 連接[^6],我們在這裡簡單回顧一下 TCP 連接建立的整個過程。

HTTPS 的 7 次握手以及 9 倍時延

tcp-3-way-handshake

圖 2 - TCP 三次握手

  1. 客戶端向服務端發送帶有 SYN 的數據段以及客戶端開始發送數據段(Segment)的初始序列號 SEQ = 100;
  2. 服務端收到數據段時,向客戶端發送帶有 SYN 和 ACK 的數據段;
    1. 通過返回 ACK = 101 確認客戶端數據段的初始序列號;
    2. 通過發送 SEQ = 300 通知客戶端,服務端開始發送數據段的初始序列號;
  3. 客戶端向服務端發送帶有 ACK 的數據段,確認服務端的初始序列號,其中包含 ACK = 301;

TCP 連接的雙方會通過三次握手確定 TCP 連接的初始序列號、窗口大小以及最大數據段,這樣通信雙方就能利用連接中的初始序列號保證雙方數據段的不重不漏、通過窗口大小控制流量並使用最大數據段避免 IP 協議對數據包的分片[^7]。

最初版本的 TCP 協議確實會通過三次通信建立 TCP 連接,在目前的大多數場景下,三次握手也是無法避免的,不過在 2014 年提出的 TCP 快啟(TCP Fast Open,TFO)卻可以在某些場景下通過一次通信建立 TCP 連接[^8]。

HTTPS 的 7 次握手以及 9 倍時延

tcp-fast-open

圖 3 - TCP 快啟

TCP 快啟策略使用存儲在客戶端的 TFO Cookie 與服務端快速建立連接。TCP 連接的客戶端向服務端發送 SYN 消息時會攜帶快啟選項,服務端會生成一個 Cookie 並將其發送至客戶端,客戶端會緩存該 Cookie,當其與服務端重新建立連接時,它會使用存儲的 Cookie 直接建立 TCP 連接,服務端驗證 Cookie 後會向客戶端發送 SYN 和 ACK 並開始傳輸數據,這也就能減少通信的次數。

TLS

TLS 的作用是在可靠的 TCP 協議上構建安全的傳輸通道,其本身是不提供可靠性保障的,我們還是需要下層可靠的傳輸層協議。在通信雙方建立可靠的 TCP 連接之後,我們就需要通過 TLS 握手交換雙方的密鑰了,在這裡我們將介紹 TLS 1.2 的連接建立過程[^9]:

HTTPS 的 7 次握手以及 9 倍時延

tls-1-2-handshake

圖 4 - TLS 1.2 建立連接

  1. 客戶端向服務端發送 Client Hello 消息,其中攜帶客戶端支持的協議版本、加密算法、壓縮算法以及客戶端生成的隨機數
  2. 服務端收到客戶端支持的協議版本、加密算法等信息後;
    1. 向客戶端發送 Server Hello 消息,並攜帶選擇特定的協議版本、加密方法、會話 ID 以及服務端生成的隨機數
    2. 向客戶端發送 Certificate 消息,即服務端的證書鏈,其中包含證書支持的域名、發行方和有效期等信息;
    3. 向客戶端發送 Server Key Exchange 消息,傳遞公鑰以及簽名等信息;
    4. 向客戶端發送可選的消息 CertificateRequest,驗證客戶端的證書;
    5. 向客戶端發送 Server Hello Done 消息,通知服務端已經發送了全部的相關信息;
  3. 客戶端收到服務端的協議版本、加密方法、會話 ID 以及證書等信息後,驗證服務端的證書;
    1. 向服務端發送 Client Key Exchange 消息,包含使用服務端公鑰加密後的隨機字符串,即預主密鑰(Pre Master Secret);
    2. 向服務端發送 Change Cipher Spec 消息,通知服務端後面的數據段會加密傳輸;
    3. 向服務端發送 Finished 消息,其中包含加密後的握手信息;
  4. 服務端收到 Change Cipher Spec 和 Finished 消息後;
    1. 向客戶端發送 Change Cipher Spec 消息,通知客戶端後面的數據段會加密傳輸;
    2. 向客戶端發送 Finished 消息,驗證客戶端的 Finished 消息並完成 TLS 握手;

TLS 握手的關鍵在於利用通信雙方生成的隨機字符串和服務端的公鑰生成一個雙方經過協商後的密鑰,通信的雙方可以使用這個對稱的密鑰加密消息防止中間人的監聽和攻擊,保證通信的安全。

在 TLS 1.2 中,我們需要 2-RTT 才能建立 TLS 連接[^10],但是 TLS 1.3 通過優化協議,將兩次往返延遲降低至一次,大幅度減少建立 TLS 連接所需要的時間,讓客戶端可以在 1-RTT 之後就能向服務端傳輸應用層數據。

這裡就不展開介紹 TLS 1.3 建立連接的過程了,除了減少常規握手下的網絡開銷,TLS 1.3 還引入了 0-RTT 的連接建立過程;60% 的網絡連接都是用戶在第一次訪問網站或者間隔一段時間後訪問時建立的,剩下的 40% 可以通過 TLS 1.3 的 0-RTT 策略解決[^11],然而該策略與 TFO 的實現原理比較相似,都是通過重用會話和緩存來實現的,所以存在一定的安全風險,使用時也應該結合業務的具體場景。

HTTP

在已經建立好 TCP 和 TLS 通道上傳輸數據是比較簡單的事情,HTTP 協議可以直接利用下層建立的可靠的、安全的通道傳輸數據。客戶端通過 TCP 的套接字接口向服務端寫入數據,服務端在接收到數據、進行處理後通過相同的途徑返回。因為整個過程需要客戶端發送請求以及服務端返回響應,所以耗時是 1-RTT。

HTTPS 的 7 次握手以及 9 倍時延

http-request-and-response

圖 5 - HTTP 請求和響應

HTTP 協議的數據交換隻會消耗 1-RTT,當客戶端和服務端僅處理一次 HTTP 請求時,從 HTTP 協議本身我們已經無法進行優化。不過隨著請求的數量逐漸增加,HTTP/2 就可以複用已經建立的 TCP 連接減少 TCP 和 TLS 握手帶來的額外開銷。

總結

當客戶端想要通過 HTTPS 請求訪問服務端時,整個過程需要經過 7 次握手並消耗 9 倍的延遲。如果客戶端和服務端因為物理距離上的限制,RTT 約為 40ms 時,第一次請求需要 ~180ms;不過如果我們想要訪問美國的服務器,RTT 約為 200ms 時,這時 HTTPS 請求的耗時為 ~900ms,這就是一個比較高的耗時了。我們來總結一下 HTTPS 協議需要 9 倍時延才能完成通信的原因:

  1. TCP 協議需要通過三次握手建立 TCP 連接保證通信的可靠性(1.5-RTT);
  2. TLS 協議會在 TCP 協議之上通過四次握手建立 TLS 連接保證通信的安全性(2-RTT);
  3. HTTP 協議會在 TCP 和 TLS 上通過一次往返發送請求並接收響應(1-RTT);

需要注意的是,本文對往返延時的計算都基於特定的場景以及特定的協議版本,網絡協議的版本在不斷更新和演進,過去忽略的問題最開始都會通過補丁的方式更新,但是最後仍然會需要從底層完成重寫。

HTTP/3 就是一個這樣的例子,它會使用基於 UDP 的 QUIC 協議進行握手,將 TCP 和 TLS 的握手過程結合起來,把 7 次握手減少到了 3 次握手,直接建立了可靠並且安全的傳輸通道,將原本 ~900ms 的耗時降低至 ~500ms,我們會在後面的文章介紹 HTTP/3 協議相關的內容。到最後,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

  • 作為傳輸層協議,QUIC 協議和 TCP 協議之間有什麼異同?
  • 為什麼可以通過 0-RTT 建立客戶端和服務端的連接?

如果對文章中的內容有疑問或者想要了解更多軟件工程上一些設計決策背後的原因,可以在博客下面留言,作者會及時回覆本文相關的疑問並選擇其中合適的主題作為後續的內容。


分享到:


相關文章: