HTTPS 的7次握手以及9倍時延
HTTP 協(xié)議(Hypertext Transfer Protocol)已經(jīng)成為互聯(lián)網(wǎng)上最常用的應(yīng)用層協(xié)議,然而其本身只是用于傳輸超文本的網(wǎng)絡(luò)協(xié)議,不會提供任何安全上的保證,使用明文在互聯(lián)網(wǎng)上傳輸數(shù)據(jù)包使得竊聽和中間人攻擊成為可能,通過 HTTP 傳輸密碼其實與在互聯(lián)網(wǎng)上裸奔也差不多。
https-banner
圖 1 - HTTPS 協(xié)議
網(wǎng)景(Netscape)在 1994 年設(shè)計了 HTTPS 協(xié)議,使用安全套接字層(Secure Sockets Layer,SSL)保證數(shù)據(jù)傳輸?shù)陌踩玔^1],隨著傳輸層安全協(xié)議(Transport Layer Security,TLS)的發(fā)展,目前我們已經(jīng)使用 TLS 取代了廢棄的 SSL 協(xié)議,不過仍然使用 SSL 證書一詞[^2]。
HTTPS 是對 HTTP 協(xié)議的擴展,我們可以使用它在互聯(lián)網(wǎng)上安全地傳輸數(shù)據(jù)[^3],然而 HTTPS 請求的發(fā)起方第一次從接收方獲取響應(yīng)需要經(jīng)過 4.5 倍的往返延遲(Round-Trip Time,RTT)。本文將詳細介紹請求發(fā)起和響應(yīng)的過程,分析為什么 HTTPS 協(xié)議需要通過 4.5-RTT 的時間獲得服務(wù)提供方的響應(yīng):
- TCP 協(xié)議 — 通信雙方通過三次握手建立 TCP 連接[^4];
- TLS 協(xié)議 — 通信雙方通過四次握手建立 TLS 連接[^5];
- HTTP 協(xié)議 — 客戶端向服務(wù)端發(fā)送請求,服務(wù)端發(fā)回響應(yīng);
這里的分析建立在特定版本的協(xié)議實現(xiàn)以及常見場景上,隨著網(wǎng)絡(luò)技術(shù)的發(fā)展,我們能夠減少需要的網(wǎng)絡(luò)通信次數(shù),本文會在對應(yīng)章節(jié)中提到一些常見的優(yōu)化方案。
TCP
HTTP 協(xié)議作為應(yīng)用層協(xié)議,它需要底層的傳輸層協(xié)議為其提供基本的數(shù)據(jù)傳輸功能,HTTP 協(xié)議一般都會使用 TCP 協(xié)議作為底層協(xié)議。為了阻止錯誤的建立歷史連接,TCP 協(xié)議通信的雙方會通過三次握手建立 TCP 連接[^6],我們在這里簡單回顧一下 TCP 連接建立的整個過程。
tcp-3-way-handshake
圖 2 - TCP 三次握手
(1) 客戶端向服務(wù)端發(fā)送帶有 SYN 的數(shù)據(jù)段以及客戶端開始發(fā)送數(shù)據(jù)段(Segment)的初始序列號 SEQ = 100;
(2) 服務(wù)端收到數(shù)據(jù)段時,向客戶端發(fā)送帶有 SYN 和 ACK的數(shù)據(jù)段;
- 通過返回 ACK = 101 確認客戶端數(shù)據(jù)段的初始序列號;
- 通過發(fā)送 SEQ = 300 通知客戶端,服務(wù)端開始發(fā)送數(shù)據(jù)段的初始序列號;
(3) 客戶端向服務(wù)端發(fā)送帶有 ACK 的數(shù)據(jù)段,確認服務(wù)端的初始序列號,其中包含 ACK = 301;
TCP 連接的雙方會通過三次握手確定 TCP 連接的初始序列號、窗口大小以及最大數(shù)據(jù)段,這樣通信雙方就能利用連接中的初始序列號保證雙方數(shù)據(jù)段的不重不漏、通過窗口大小控制流量并使用最大數(shù)據(jù)段避免 IP 協(xié)議對數(shù)據(jù)包的分片[^7]。
最初版本的 TCP 協(xié)議確實會通過三次通信建立 TCP 連接,在目前的大多數(shù)場景下,三次握手也是無法避免的,不過在 2014 年提出的 TCP 快啟(TCP Fast Open,TFO)卻可以在某些場景下通過一次通信建立 TCP 連接[^8]。
tcp-fast-open
圖 3 - TCP 快啟
TCP 快啟策略使用存儲在客戶端的 TFO Cookie 與服務(wù)端快速建立連接。TCP 連接的客戶端向服務(wù)端發(fā)送 SYN 消息時會攜帶快啟選項,服務(wù)端會生成一個 Cookie 并將其發(fā)送至客戶端,客戶端會緩存該 Cookie,當(dāng)其與服務(wù)端重新建立連接時,它會使用存儲的 Cookie 直接建立 TCP 連接,服務(wù)端驗證 Cookie 后會向客戶端發(fā)送 SYN 和 ACK 并開始傳輸數(shù)據(jù),這也就能減少通信的次數(shù)。
TLS
TLS 的作用是在可靠的 TCP 協(xié)議上構(gòu)建安全的傳輸通道,其本身是不提供可靠性保障的,我們還是需要下層可靠的傳輸層協(xié)議。在通信雙方建立可靠的 TCP 連接之后,我們就需要通過 TLS 握手交換雙方的密鑰了,在這里我們將介紹 TLS 1.2 的連接建立過程[^9]:
tls-1-2-handshake
圖 4 - TLS 1.2 建立連接
(1) 客戶端向服務(wù)端發(fā)送 Client Hello 消息,其中攜帶客戶端支持的協(xié)議版本、加密算法、壓縮算法以及客戶端生成的隨機數(shù);
(2) 服務(wù)端收到客戶端支持的協(xié)議版本、加密算法等信息后;
- 向客戶端發(fā)送 Server Hello 消息,并攜帶選擇特定的協(xié)議版本、加密方法、會話 ID 以及服務(wù)端生成的隨機數(shù);
- 向客戶端發(fā)送 Certificate 消息,即服務(wù)端的證書鏈,其中包含證書支持的域名、發(fā)行方和有效期等信息;
- 向客戶端發(fā)送 Server Key Exchange 消息,傳遞公鑰以及簽名等信息;
- 向客戶端發(fā)送可選的消息 CertificateRequest,驗證客戶端的證書;
- 向客戶端發(fā)送 Server Hello Done 消息,通知服務(wù)端已經(jīng)發(fā)送了全部的相關(guān)信息;
(3) 客戶端收到服務(wù)端的協(xié)議版本、加密方法、會話 ID 以及證書等信息后,驗證服務(wù)端的證書;
- 向服務(wù)端發(fā)送 Client Key Exchange 消息,包含使用服務(wù)端公鑰加密后的隨機字符串,即預(yù)主密鑰(Pre Master Secret);
- 向服務(wù)端發(fā)送 Change Cipher Spec 消息,通知服務(wù)端后面的數(shù)據(jù)段會加密傳輸;
- 向服務(wù)端發(fā)送 Finished 消息,其中包含加密后的握手信息;
(4) 服務(wù)端收到 Change Cipher Spec 和 Finished 消息后;
- 向客戶端發(fā)送 Change Cipher Spec 消息,通知客戶端后面的數(shù)據(jù)段會加密傳輸;
- 向客戶端發(fā)送 Finished 消息,驗證客戶端的 Finished 消息并完成 TLS 握手;
TLS 握手的關(guān)鍵在于利用通信雙方生成的隨機字符串和服務(wù)端的公鑰生成一個雙方經(jīng)過協(xié)商后的密鑰,通信的雙方可以使用這個對稱的密鑰加密消息防止中間人的監(jiān)聽和攻擊,保證通信的安全。
在 TLS 1.2 中,我們需要 2-RTT 才能建立 TLS 連接[^10],但是 TLS 1.3 通過優(yōu)化協(xié)議,將兩次往返延遲降低至一次,大幅度減少建立 TLS 連接所需要的時間,讓客戶端可以在 1-RTT 之后就能向服務(wù)端傳輸應(yīng)用層數(shù)據(jù)。
這里就不展開介紹 TLS 1.3 建立連接的過程了,除了減少常規(guī)握手下的網(wǎng)絡(luò)開銷,TLS 1.3 還引入了 0-RTT 的連接建立過程;60% 的網(wǎng)絡(luò)連接都是用戶在第一次訪問網(wǎng)站或者間隔一段時間后訪問時建立的,剩下的 40% 可以通過 TLS 1.3 的 0-RTT 策略解決[^11],然而該策略與 TFO 的實現(xiàn)原理比較相似,都是通過重用會話和緩存來實現(xiàn)的,所以存在一定的安全風(fēng)險,使用時也應(yīng)該結(jié)合業(yè)務(wù)的具體場景。
HTTP
在已經(jīng)建立好 TCP 和 TLS 通道上傳輸數(shù)據(jù)是比較簡單的事情,HTTP 協(xié)議可以直接利用下層建立的可靠的、安全的通道傳輸數(shù)據(jù)??蛻舳送ㄟ^ TCP 的套接字接口向服務(wù)端寫入數(shù)據(jù),服務(wù)端在接收到數(shù)據(jù)、進行處理后通過相同的途徑返回。因為整個過程需要客戶端發(fā)送請求以及服務(wù)端返回響應(yīng),所以耗時是 1-RTT。
http-request-and-response
圖 5 - HTTP 請求和響應(yīng)
HTTP 協(xié)議的數(shù)據(jù)交換只會消耗 1-RTT,當(dāng)客戶端和服務(wù)端僅處理一次 HTTP 請求時,從 HTTP 協(xié)議本身我們已經(jīng)無法進行優(yōu)化。不過隨著請求的數(shù)量逐漸增加,HTTP/2 就可以復(fù)用已經(jīng)建立的 TCP 連接減少 TCP 和 TLS 握手帶來的額外開銷。
總結(jié)
當(dāng)客戶端想要通過 HTTPS 請求訪問服務(wù)端時,整個過程需要經(jīng)過 7 次握手并消耗 9 倍的延遲。如果客戶端和服務(wù)端因為物理距離上的限制,RTT 約為 40ms 時,第一次請求需要 ~180ms;不過如果我們想要訪問美國的服務(wù)器,RTT 約為 200ms 時,這時 HTTPS 請求的耗時為 ~900ms,這就是一個比較高的耗時了。我們來總結(jié)一下 HTTPS 協(xié)議需要 9 倍時延才能完成通信的原因:
- TCP 協(xié)議需要通過三次握手建立 TCP 連接保證通信的可靠性(1.5-RTT);
- TLS 協(xié)議會在 TCP 協(xié)議之上通過四次握手建立 TLS 連接保證通信的安全性(2-RTT);
- HTTP 協(xié)議會在 TCP 和 TLS 上通過一次往返發(fā)送請求并接收響應(yīng)(1-RTT);
需要注意的是,本文對往返延時的計算都基于特定的場景以及特定的協(xié)議版本,網(wǎng)絡(luò)協(xié)議的版本在不斷更新和演進,過去忽略的問題最開始都會通過補丁的方式更新,但是最后仍然會需要從底層完成重寫。
HTTP/3 就是一個這樣的例子,它會使用基于 UDP 的 QUIC 協(xié)議進行握手,將 TCP 和 TLS 的握手過程結(jié)合起來,把 7 次握手減少到了 3 次握手,直接建立了可靠并且安全的傳輸通道,將原本 ~900ms 的耗時降低至 ~500ms,我們會在后面的文章介紹 HTTP/3 協(xié)議相關(guān)的內(nèi)容。到最后,我們還是來看一些比較開放的相關(guān)問題,有興趣的讀者可以仔細思考一下下面的問題:
- 作為傳輸層協(xié)議,QUIC 協(xié)議和 TCP 協(xié)議之間有什么異同?
- 為什么可以通過 0-RTT 建立客戶端和服務(wù)端的連接?