TLS握手優化詳解


 

 

隨着 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 106.187.88.156

測試結果如下(去掉了部分不重要的信息):

[√] Check arguments.
[√] Solve 114.215.116.12:
    │ Got 1 result:
    │ 114.215.116.12
[√] Solve 106.187.88.156:
    │ Got 1 result:
    │ 106.187.88.156
[√] 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… │   ×   
    │ 106.187.88.156 │  0  │   ×   │ 2F8143213E3B9… │ 1BFE00946… │   ×   
    │ 106.187.88.156 │  1  │   √   │ 2F8143213E3B9… │ 1BFE00946… │   ×   
    │ 106.187.88.156 │  2  │   √   │ 2F8143213E3B9… │ 1BFE00946… │   ×   
    │ 106.187.88.156 │  3  │   √   │ 2F8143213E3B9… │ 1BFE00946… │   ×   
    │ 106.187.88.156 │  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… │   √   
    │ 106.187.88.156 │  0  │   √   │ 96A21A1849BD4… │ C15030CF8… │   √   
    │ 106.187.88.156 │  1  │   √   │ 96A21A1849BD4… │ C15030CF8… │   √   
    │ 106.187.88.156 │  2  │   √   │ 96A21A1849BD4… │ C15030CF8… │   √   
    │ 106.187.88.156 │  3  │   √   │ 96A21A1849BD4… │ C15030CF8… │   √   
    │ 106.187.88.156 │  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。

 

來源:Jerry Qu的小站

原文:http://t.cn/RUCdjKs

版權:本文版權歸原作者所有


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM