28 丨 連接太慢該怎么辦:HTTPS的優化


說明《透視HTTP協議》是 羅劍鋒 (奇虎360技術專家)在極客時間開的一門專欄課,筆者記錄一下學習筆記,僅供參考。

你可能或多或少聽別人說過,“HTTPS 的連接很慢”。那么“慢”的原因是什么呢?

通過前兩講的學習,你可以看到,HTTPS 連接大致上可以划分為兩個部分,第一個是建立連接時的非對稱加密握手,第二個是握手后的對稱加密報文傳輸

       由於目前流行的 AES、ChaCha20 性能都很好,還有硬件優化,報文傳輸的性能損耗可以說是非常地小,小到幾乎可以忽略不計了。所以,通常所說的“HTTPS 連接慢”指的就是剛開始建立連接的那段時間。

       在 TCP 建連之后,正式數據傳輸之前,HTTPS 比 HTTP 增加了一個 TLS 握手的步驟,這個步驟最長可以花費兩個消息往返,也就是 2-RTT。而且在握手消息的網絡耗時之外,還會有其他的一些“隱形”消耗,比如:

產生用於密鑰交換的臨時公私鑰對(ECDHE);

驗證證書時訪問 CA 獲取 CRL 或者 OCSP;

非對稱加密解密處理“Pre-Master”。

       在最差的情況下,也就是不做任何的優化措施,HTTPS 建立連接可能會比 HTTP 慢上幾百毫秒甚至幾秒,這其中既有網絡耗時,也有計算耗時,就會讓人產生“打開一個 HTTPS 網站好慢啊”的感覺。

       不過剛才說的情況早就是“過去時”了,現在已經有了很多行之有效的 HTTPS 優化手段,運用得好可以把連接的額外耗時降低到幾十毫秒甚至是“零”。

我畫了一張圖,把 TLS 握手過程中影響性能的部分都標記了出來,對照着它就可以“有的放矢”地來優化 HTTPS。

 

硬件優化

       在計算機世界里的“優化”可以分成“硬件優化”和“軟件優化”兩種方式,先來看看有哪些硬件的手段。

       硬件優化,說白了就是“花錢”。但花錢也是有門道的,要“有錢用在刀刃上”,不能大把的銀子撒出去“只聽見響”。

       HTTPS 連接是計算密集型,而不是 I/O 密集型。所以,如果你花大價錢去買網卡、帶寬、SSD 存儲就是“南轅北轍”了,起不到優化的效果。

       那該用什么樣的硬件來做優化呢?

       首先,你可以選擇更快的 CPU,最好還內建 AES 優化,這樣即可以加速握手,也可以加速傳輸。

       其次,你可以選擇“SSL 加速卡”,加解密時調用它的 API,讓專門的硬件來做非對稱加解密,分擔 CPU 的計算壓力。

       不過“SSL 加速卡”也有一些缺點,比如升級慢、支持算法有限,不能靈活定制解決方案等。

       所以,就出現了第三種硬件加速方式:“SSL 加速服務器”,用專門的服務器集群來徹底“卸載”TLS 握手時的加密解密計算,性能自然要比單純的“加速卡”要強大的多。

軟件優化

       不過硬件優化方式中除了 CPU,其他的通常可不是靠簡單花錢就能買到的,還要有一些開發適配工作,有一定的實施難度。比如,“加速服務器”中關鍵的一點是通信必須是“異步”的,不能阻塞應用服務器,否則加速就沒有意義了。

       所以,軟件優化的方式相對來說更可行一些,性價比高,能夠“少花錢,多辦事”。

軟件方面的優化還可以再分成兩部分:一個是軟件升級,一個是協議優化

       軟件升級實施起來比較簡單,就是把現在正在使用的軟件盡量升級到最新版本,比如把 Linux 內核由 2.x 升級到 4.x,把 Nginx 由 1.6 升級到 1.16,把 OpenSSL 由 1.0.1 升級到 1.1.0/1.1.1。

       由於這些軟件在更新版本的時候都會做性能優化、修復錯誤,只要運維能夠主動配合,這種軟件優化是最容易做的,也是最容易達成優化效果的。

       但對於很多大中型公司來說,硬件升級或軟件升級都是個棘手的問題,有成千上萬台各種型號的機器遍布各個機房,逐一升級不僅需要大量人手,而且有較高的風險,可能會影響正常的線上服務。

       所以,在軟硬件升級都不可行的情況下,我們最常用的優化方式就是在現有的環境下挖掘協議自身的潛力。

協議優化

       從剛才的 TLS 握手圖中你可以看到影響性能的一些環節,協議優化就要從這些方面着手,先來看看核心的密鑰交換過程。

       如果有可能,應當盡量采用 TLS1.3,它大幅度簡化了握手的過程,完全握手只要 1-RTT,而且更加安全。

       如果暫時不能升級到 1.3,只能用 1.2,那么握手時使用的密鑰交換協議應當盡量選用橢圓曲線的 ECDHE 算法。它不僅運算速度快,安全性高,還支持“False Start”,能夠把握手的消息往返由 2-RTT 減少到 1-RTT,達到與 TLS1.3 類似的效果。

       另外,橢圓曲線也要選擇高性能的曲線,最好是 x25519,次優選擇是 P-256。對稱加密算法方面,也可以選用“AES_128_GCM”,它能比“AES_256_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;

復制代碼

證書優化

       除了密鑰交換,握手過程中的證書驗證也是一個比較耗時的操作,服務器需要把自己的證書鏈全發給客戶端,然后客戶端接收后再逐一驗證。

       這里就有兩個優化點,一個是證書傳輸,一個是證書驗證

       服務器的證書可以選擇橢圓曲線(ECDSA)證書而不是 RSA 證書,因為 224 位的 ECC 相當於 2048 位的 RSA,所以橢圓曲線證書的“個頭”要比 RSA 小很多,即能夠節約帶寬也能減少客戶端的運算量,可謂“一舉兩得”。

       客戶端的證書驗證其實是個很復雜的操作,除了要公鑰解密驗證多個證書簽名外,因為證書還有可能會被撤銷失效,客戶端有時還會再去訪問 CA,下載 CRL 或者 OCSP 數據,這又會產生 DNS 查詢、建立連接、收發數據等一系列網絡通信,增加好幾個 RTT。

CRL(Certificate revocation list,證書吊銷列表)由 CA 定期發布,里面是所有被撤銷信任的證書序號,查詢這個列表就可以知道證書是否有效。

       但 CRL 因為是“定期”發布,就有“時間窗口”的安全隱患,而且隨着吊銷證書的增多,列表會越來越大,一個 CRL 經常會上 MB。想象一下,每次需要預先下載幾 M 的“無用數據”才能連接網站,實用性實在是太低了。

       所以,現在 CRL 基本上不用了,取而代之的是 OCSP(在線證書狀態協議,Online Certificate Status Protocol),向 CA 發送查詢請求,讓 CA 返回證書的有效狀態。

但 OCSP 也要多出一次網絡請求的消耗,而且還依賴於 CA 服務器,如果 CA 服務器很忙,那響應延遲也是等不起的。

       於是又出來了一個“補丁”,叫“OCSP Stapling”(OCSP 裝訂),它可以讓服務器預先訪問 CA 獲取 OCSP 響應,然后在握手時隨着證書一起發給客戶端,免去了客戶端連接 CA 服務器查詢的時間。

會話復用

       到這里,我們已經討論了四種 HTTPS 優化手段(硬件優化、軟件優化、協議優化、證書優化),那么,還有沒有其他更好的方式呢?

       我們再回想一下 HTTPS 建立連接的過程:先是 TCP 三次握手,然后是 TLS 一次握手。這后一次握手的重點是算出主密鑰“Master Secret”,而主密鑰每次連接都要重新計算,未免有點太浪費了,如果能夠把“辛辛苦苦”算出來的主密鑰緩存一下“重用”,不就可以免去了握手和計算的成本了嗎?

       這種做法就叫“會話復用”(TLS session resumption),和 HTTP Cache 一樣,也是提高 HTTPS 性能的“大殺器”,被瀏覽器和服務器廣泛應用。

       會話復用分兩種,第一種叫“Session ID”,就是客戶端和服務器首次連接后各自保存一個會話的 ID 號,內存里存儲主密鑰和其他相關的信息。當客戶端再次連接時發一個 ID 過來,服務器就在內存里找,找到就直接用主密鑰恢復會話狀態,跳過證書驗證和密鑰交換,只用一個消息往返就可以建立安全通信。

       實驗環境的端口 441 實現了“Session ID”的會話復用,你可以訪問 URI
https://www.chrono.com:441/28-1”,刷新幾次,用 Wireshark 抓包看看實際的效果。

 

Handshake Protocol: Client Hello

 

    Version: TLS 1.2 (0x0303)

 

    Session ID: 13564734eeec0a658830cd…

 

    Cipher Suites Length: 34

 

 

 

 

 

Handshake Protocol: Server Hello

 

    Version: TLS 1.2 (0x0303)

 

    Session ID: 13564734eeec0a658830cd…

 

    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

復制代碼

通過抓包可以看到,服務器在“ServerHello”消息后直接發送了“Change Cipher Spec”和“Finished”消息,復用會話完成了握手。

 

會話票證

       “Session ID”是最早出現的會話復用技術,也是應用最廣的,但它也有缺點,服務器必須保存每一個客戶端的會話數據,對於擁有百萬、千萬級別用戶的網站來說存儲量就成了大問題,加重了服務器的負擔。

       於是,又出現了第二種“Session Ticket”方案。

       它有點類似 HTTP 的 Cookie,存儲的責任由服務器轉移到了客戶端,服務器加密會話信息,用“New Session Ticket”消息發給客戶端,讓客戶端保存。

重連的時候,客戶端使用擴展“session_ticket”發送“Ticket”而不是“Session ID”,服務器解密后驗證有效期,就可以恢復會話,開始加密通信。

       這個過程也可以在實驗環境里測試,端口號是 442,URI 是“https://www.chrono.com:442/28-1”。

       不過“Session Ticket”方案需要使用一個固定的密鑰文件(ticket_key)來加密 Ticket,為了防止密鑰被破解,保證“前向安全”,密鑰文件需要定期輪換,比如設置為一小時或者一天。

預共享密鑰

       “False Start”“Session ID”“Session Ticket”等方式只能實現 1-RTT,而 TLS1.3 更進一步實現了“0-RTT”,原理和“Session Ticket”差不多,但在發送 Ticket 的同時會帶上應用數據(Early Data),免去了 1.2 里的服務器確認步驟,這種方式叫“Pre-shared Key”,簡稱為“PSK”。

 

       但“PSK”也不是完美的,它為了追求效率而犧牲了一點安全性,容易受到“重放攻擊”(Replay attack)的威脅。黑客可以截獲“PSK”的數據,像復讀機那樣反復向服務器發送。

解決的辦法是只允許安全的 GET/HEAD 方法(參見第 10 講),在消息里加入時間戳、“nonce”驗證,或者“一次性票證”限制重放。

小結

可以有多種硬件和軟件手段減少網絡耗時和計算耗時,讓 HTTPS 變得和 HTTP 一樣快,最可行的是軟件優化;

應當盡量使用 ECDHE 橢圓曲線密碼套件,節約帶寬和計算量,還能實現“False Start”;

服務器端應當開啟“OCSP Stapling”功能,避免客戶端訪問 CA 去驗證證書;

會話復用的效果類似 Cache,前提是客戶端必須之前成功建立連接,后面就可以用“Session ID”“Session Ticket”等憑據跳過密鑰交換、證書驗證等步驟,直接開始加密通信。

課下作業

你能比較一下“Session ID”“Session Ticket”“PSK”這三種會話復用手段的異同嗎?

你覺得哪些優化手段是你在實際工作中能用到的?應該怎樣去用?

 


免責聲明!

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



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