TLS 握手優化詳解
提醒:本文最後更新於 913 天前,文中所描述的資訊可能已發生改變,請謹慎使用。
隨著 HTTP/2 的逐漸普及,以及國內網路環境越來越糟糕(運營商劫持和篡改),HTTPS 已經開始成為主流。HTTPS 在 TCP 和 HTTP 之間增加了 TLS(Transport Layer Security,傳輸層安全),提供了內容加密、身份認證和資料完整性三大功能,同時也給 Web 效能優化帶來新的挑戰。上次寫的「使用 BoringSSL 優化 HTTPS 加密演算法選擇」一文中,我介紹瞭如何針對不同平臺啟用最合適的傳輸加密演算法。本篇文章我打算繼續寫 HTTPS 優化 —— TLS 握手優化。
TLS 的前身是 SSL(Secure Sockets Layer,安全套接字層),由網景公司開發,後來被 IETF 標準化並改名。通常沒有特別說明時,SSL 和 TLS 指的是同一個協議,不做嚴格區分。
TLS 握手
在傳輸應用資料之前,客戶端必須與服務端協商金鑰、加密演算法等資訊,服務端還要把自己的證書發給客戶端表明其身份,這些環節構成 TLS 握手過程,如下圖所示:
可以看到,假設服務端和客戶端之間單次傳輸耗時 28ms,那麼客戶端需要等到 168ms 之後才能開始傳送 HTTP 請求報文,這還沒把客戶端和服務端處理時間算進去。光是 TLS 握手就需要消耗兩個 RTT(Round-Trip Time,往返時間),這就是造成 HTTPS 更慢的主要原因。當然,HTTPS 要求資料加密傳輸,加解密相比 HTTP 也會帶來額外的開銷,不過對稱加密本來就很快,加上硬體效能越來越好,所以這部分開銷還好。
詳細的 TLS 握手過程這裡就不介紹了,大家可以通過這兩篇文章去了解:
通過 Wireshark 抓包可以清楚地看到完整 TLS 握手過程所需的兩個 RTT,如下圖:
False Start
False Start 有搶跑的意思,意味著不按規則行事。TLS False Start 是指客戶端在傳送 Change Cipher Spec Finished 同時傳送應用資料(如 HTTP 請求),服務端在 TLS 握手完成時直接返回應用資料(如 HTTP 響應)。這樣,應用資料的傳送實際上並未等到握手全部完成,故謂之搶跑。這個過程如下圖所示:
可以看到,啟用 False Start 之後,TLS 階段只需要一次 RTT 就可以開始傳輸應用資料。False Start 相當於客戶端提前傳送加密後的應用資料,不需要修改 TLS 協議,目前大部分瀏覽器預設都會啟用,但也有一些前提條件:
- 服務端必須在 Server Hello 握手中通過 NPN(Next Protocol Negotiation,下一代協議協商,Google 在 SPDY 協議中開發的 TLS 擴充套件,用於握手階段協商應用協議)或 ALPN(Application Layer Protocol Negotiation,應用層協議協商,NPN 的官方修訂版)表明自己支援的 HTTP 協議,例如:http/1.1、http/2;
- 使用支援前向安全性(Forward Secrecy)的加密演算法。False Start 在尚未完成握手時就傳送了應用資料,Forward Secrecy 可以提高安全性;
通過 Wireshark 抓包可以清楚地看到 False Start 帶來的好處(服務端的 ChangeCipherSpec 出現在 158 號包中,但在之前的 155 號包中,客戶端已經發出了請求,相當於 TLS 握手只消耗了一個 RTT):
Certificate
TLS 的身份認證是通過證書信任鏈完成的,瀏覽器從站點證書開始遞迴校驗父證書,直至出現信任的根證書(根證書列表一般內置於作業系統,Firefox 自己維護)。站點證書是在 TLS 握手階段,由服務端傳送的。
Certificate-Chain
配置服務端證書鏈時,有兩點需要注意:1)證書是在握手期間傳送的,由於 TCP 初始擁塞視窗的存在,如果證書太長可能會產生額外的往返開銷;2)如果證書沒包含中間證書,大部分瀏覽器可以正常工作,但會暫停驗證並根據子證書指定的父證書 URL 自己獲取中間證書。這個過程會產生額外的 DNS 解析、建立 TCP 連線等開銷,非常影響效能。
配置證書鏈的最佳實踐是隻包含站點證書和中間證書,不要包含根證書,也不要漏掉中間證書。大部分證書鏈都是「站點證書 - 中間證書 - 根證書」這樣三級,這時服務端只需要傳送前兩個證書即可。但也有的證書鏈有四級,那就需要傳送站點證書外加兩個中間證書了。
通過 Wireshark 可以檢視服務端傳送的證書鏈情況,如下圖。可以看到本站傳送了兩個證書,共 2270 位元組,被分成 2 個 TCP 段來傳輸。這已經算小的了,理想的證書鏈應該控制在 3kb 以內。
ECC Certificate
如果需要進一步減小證書大小,可以選擇 ECC(Elliptic Curve Cryptography,橢圓曲線密碼學)證書。256 位的 ECC Key 等同於 3072 位的 RSA Key,在確保安全性的同時,體積大幅減小。下面是一個對比:
對稱加密 Key 長度 | RSA Key 長度 | ECC Key 長度 |
---|---|---|
80 | 1024 | 160 |
112 | 2048 | 224 |
128 | 3072 | 256 |
192 | 7680 | 384 |
256 | 15360 | 521 |
如果證書提供商支援 ECC 證書,使用以下命令生成 CSR(Certificate Signing Request,證書籤名請求)檔案並提交給提供商,就可以獲得 ECC 證書:
openssl ecparam -genkey -name secp256r1 | openssl ec -out ecc.key
openssl req -new -key ecc.key -out ecc.csr
以上命令中可以選擇的演算法有 secp256r1 和 secp384r1,secp521r1 已被 Chrome 和 Firefox 拋棄。
ECC 證書這麼好,為什麼沒有普及呢?最主要的原因是它的支援情況並不好。例如 Windows XP 不支援,導致使用 ECC 證書的網站在 Windows XP 上只有 Firefox 能訪問(Firefox 證書那一套完全自己實現,不依賴作業系統)。另外,Android 平臺也只有 Android 4+ 才支援 ECC 證書。所以,確定使用 ECC 證書前需要明確使用者系統分佈情況。
Session Resumption
另外一個提高 TLS 握手效率的機制是會話複用。會話複用的原理很簡單,將第一次握手辛辛苦苦算出來的對稱金鑰存起來,後續請求中直接使用。這樣可以節省證書傳送等開銷,也可以將 TLS 握手所需 RTT 減少到一個,如下圖所示:
可以看到會話複用機制生效時,雙方几乎不怎麼交換資料就協商好金鑰了,這是怎麼做到的呢?
Session Identifier
Session Identifier(會話識別符號),是 TLS 握手中生成的 Session ID。服務端可以將 Session ID 協商後的資訊存起來,瀏覽器也可以儲存 Session ID,並在後續的 ClientHello 握手中帶上它,如果服務端能找到與之匹配的資訊,就可以完成一次快速握手。
Session Ticket
Session Identifier 機制有一些弊端,例如:1)負載均衡中,多機之間往往沒有同步 Session 資訊,如果客戶端兩次請求沒有落在同一臺機器上就無法找到匹配的資訊;2)服務端儲存 Session ID 對應的資訊不好控制失效時間,太短起不到作用,太長又佔用服務端大量資源。
而 Session Ticket(會話記錄單)可以解決這些問題,Session Ticket 是用只有服務端知道的安全金鑰加密過的會話資訊,最終儲存在瀏覽器端。瀏覽器如果在 ClientHello 時帶上了 Session Ticket,只要伺服器能成功解密就可以完成快速握手。
配置 Session Ticket 策略後,通過 Wireshark 可以看到服務端傳送 Ticket 的過程:
以下是 Session Resumption 機制生效時的握手情況,可以看到沒有傳送證書等環節:
測試
Github 上有一個名為 rfc5077 的專案,非常適合用來測試服務端對 Session ID 和 Session Ticket 這兩種 TLS 會話複用機制的支援情況。下面簡單介紹如何使用這個工具。
首先,安裝工具所需依賴(以下所有步驟僅在 Ubuntu 14.04.4 LTS 測試通過):
sudo apt-get install -y pkg-config libssl-dev libev-dev libpcap-dev libgnutls-dev libnss3-dev
然後就可以獲取原始碼,開始編譯:
git clone https://github.com/vincentbernat/rfc5077.git
cd rfc5077/
git submodule init
git submodule update
make
編譯完成後,當前目錄會出現多個可執行檔案。這裡我們只會用到 rfc5077-client
,它的用法是這樣的:
./rfc5077-client [-p {port}] [-s {sni name}] [-4] host [host ...]
-p
用於指定連線的埠,預設是 443;-s
用於指定 SNI,如果同 IP 同埠部署了多個 HTTPS 網站,需要通過這個引數指定 SNI;-4
表示只使用 IPV4 地址;- 命令最後需要可以跟一個或多個 HOST(域名或 IP);
這個工具會先禁用 Session Ticket 將所有 HOST 都測試一遍,然後開啟 Session Ticket 再測試一遍。下面是對本站兩個 IP 進行測試的命令:
./rfc5077-client -s imququ.com 114.215.116.12 139.162.98.188
測試結果如下(去掉了部分不重要的資訊):
[√] Check arguments.
[√] Solve 114.215.116.12:
│ Got 1 result:
│ 114.215.116.12
[√] Solve 139.162.98.188:
│ Got 1 result:
│ 139.162.98.188
[√] Using SNI name imququ.com.
[√] Prepare tests.
[√] Run tests without use of tickets.
[√] Display result set:
│ IP address │ Try │ Reuse │ SSL Session ID │ Master key │ Ticket
│ ───────────────┼─────┼───────┼────────────────┼────────────┼────────
│ 114.215.116.12 │ 0 │ × │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 1 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 2 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 3 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 114.215.116.12 │ 4 │ √ │ BAF0834EA3896… │ 132A7A2DC… │ ×
│ 139.162.98.188 │ 0 │ × │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 1 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 2 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 3 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
│ 139.162.98.188 │ 4 │ √ │ 2F8143213E3B9… │ 1BFE00946… │ ×
[√] Dump results to file.
[√] Run tests with use of tickets.
[√] Display result set:
│ IP address │ Try │ Reuse │ SSL Session ID │ Master key │ Ticket
│ ───────────────┼─────┼───────┼────────────────┼────────────┼────────
│ 114.215.116.12 │ 0 │ × │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 1 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 2 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 3 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 114.215.116.12 │ 4 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 0 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 1 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 2 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 3 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
│ 139.162.98.188 │ 4 │ √ │ 96A21A1849BD4… │ C15030CF8… │ √
[√] Dump results to file.
從以上結果可以看出:禁用 Session Ticket 時,每次連線到不同 IP 都會導致 Session 無法複用;而啟用 Session Ticket 後,不同 IP 之間也可以複用 Session。符合前面的結論。
值得注意的是,為了讓一臺伺服器生成的 Session Ticket 能被另外伺服器承認,往往需要對 Web Server 進行額外配置。例如在 Nginx 中,就需要通過 ssl_session_ticket_key
引數讓多臺機器使用相同的 key 檔案,否則 Nginx 會使用隨機生成的 key 檔案,無法複用 Session Ticket。出於安全考慮,key 檔案應該定期更換,並且確保換下來的 key 檔案被徹底銷燬。
OCSP Stapling
出於某些原因,證書頒發者有時候需要作廢某些證書。那麼證書使用者(例如瀏覽器)如何知道一個證書是否已被作廢呢?通常有兩種方式:CRL(Certificate Revocation List,證書撤銷名單)和 OCSP(Online Certificate Status Protocol,線上證書狀態協議)。
CRL 是由證書頒發機構定期更新的一個列表,包含了所有已被作廢的證書,瀏覽器可以定期下載這個列表用於驗證證書合法性。不難想象,CRL 會隨著時間推移變得越來越大,而且實時性很難得到保證。OCSP 是一個線上查詢介面,瀏覽器可以實時查詢單個證書的合法性。在每個證書的詳細資訊中,都可以找到對應頒發機構的 CRL 和 OCSP 地址。
OCSP 的問題在於,某些客戶端會在 TLS 握手階段進一步協商時,實時查詢 OCSP 介面,並在獲得結果前阻塞後續流程,這對效能影響很大。而 OCSP Stapling(OCSP 封套),是指服務端在證書鏈中包含頒發機構對證書的 OCSP 查詢結果,從而讓瀏覽器跳過自己去驗證的過程。服務端有更快的網路,獲取 OCSP 響應更容易,也可以將 OCSP 響應快取起來。
OCSP 響應本身經過了數字簽名,無法偽造,所以 OCSP Stapling 技術既提高了握手效率,也不會影響安全性。啟用這項技術後,也可以通過 Wireshark 來驗證:
可以看到,服務端在傳送完證書後,緊接著又發來了它的 OCSP 響應,從而避免了瀏覽器自己去驗證證書造成阻塞。需要注意的是,OCSP Stapling 只能包含一個 OCSP 響應,瀏覽器還是可能自己去驗證中間證書。另外,OCSP 響應本身會佔用幾 kb 的大小。
OCSP Stapling 功能需要 Web Server 的支援,主流的 Nginx、Apache 和 H2O 都支援 —— 但同時還取決於使用的 SSL 庫 —— 例如 BoringSSL 不支援 OCSP Stapling,使用 BoringSSL + Nginx 就無法開啟 OCSP Stapling。
如何使用 Nginx 配置本文這些策略,可以參考我之前的文章:本部落格 Nginx 配置之效能篇。
最後,強烈推薦 Qualys SSL Labs 的 SSL Server Test 工具,可以幫你查出 HTTPS 很多配置上的問題。本部落格的測試結果見這裡。
--EOF--
發表於 2015-11-08 20:45:17 ,並被新增「 HTTPS 、 效能優化 、 協議 」標籤 ,最後修改於 2016-05-22 17:58:34 。檢視本文 Markdown 版本 »
提醒:本文最後更新於 913 天前,文中所描述的資訊可能已發生改變,請謹慎使用。