HTTPS優化探索與實踐
HTTPS 是網際網路安全的基礎之一,然而引入 HTTPS 卻會帶來效能上的損耗。本文作者深入解析了 HTTPS 協議優化的各個方面,對實戰很有幫助。
2012 年斯諾登(Edward Snowden)爆出稜鏡門事件後,網際網路安全問題日益得到大家的重視。去年 Apple 宣佈 2017 年 1 月 1 日之前實現所有的 App 能夠安全地接入伺服器,這項宣告來自於 iOS9 時代的應用程式安全傳輸功能(App Transport Security)。逾期沒有采用 HTTPS 的 app 將無法通過稽核並遭到下架。同年美圖也在 11 月完成了全網的 HTTPS 改造,將服務的安全級別到了一個新的高度。
本文將科普式的介紹 HTTPS 協議以及美圖在 HTTPS 優化方面的探索與實踐。
Abstract
限於篇幅,本文不對 TLS/SSL 協議的安全性做過多的假設與討論,將圍繞 HTTPS 的體驗(速度)優化介紹一下幾個方面:
-
TLS/SSL 協議淺析
-
HTTPS 優化探索
-
HTTPS 優化實踐
聽到 SSL 協議很多同學可能立刻會想起一個問題:SSL 協議和 TLS 協議到底有什麼區別呢?
HTTPS是基於 SSL的加密傳輸協議,使用 SSL來協商加密金鑰。
SSL 協議最早是由網景公司(Netscape)開發,但是隨著網景的沒落,現在由 IETF 負責維護,最初的版本也已經重新冠名 TLS
為了方便記錄,本文後續將使用 SSL協議來代指 SSL/TLS 協議。
截止目前 SSL/TLS協議族中有7種協議:
SSL v1, SSL v2, SSL v3, TLS v1.0, TLS v1.1, TLS v1.2, TLS v1.3(draft):
-
SSL v1 從未正式公開。
-
SSL v2 協議設計有缺陷,不安全。
-
SSL v3 老舊過時,缺乏一些新的金鑰特性。
-
TLS v1.0 在很大程度上是安全的,至少沒有曝光重大的安全漏洞。
-
TLS v1.1 和 TLS v1.2 目前都沒有著名的安全漏洞曝光。
-
TLS v1.3 仍然在草案階段,而且有待時間檢驗。
常用 Linux 發行版預設 OpenSSL 版本:
協議棧關係
我們先來看看 SSL 協議到底工作在哪裡。
TCP 7 層協議棧是我們都熟悉的內容,SSL 協議是工作在 表示層的協議。這也就是說 SSL 需TCP/UDP之上工作;在HTTP/FTP/SNMP等協議之前建立會話。顧名思義 HTTPS 就是基於 SSL 協議的加密的HTTP協議。
SSL 協議棧內容
整個 SSL 協議棧包含了 三種型別的協議:
-
握手協議:用於協商 SSL 金鑰
-
記錄協議:用於記錄 SSL 會話相關資訊
-
警報協議:用於通知對端停止 SSL 會話
這裡我們結合抓包來重點看一下 握手協議的工作過程。
SSL:握手過程
先來一張圖,看一看 SSL 握手的過程:
圖上藍色部分是 TCP 的握手,灰色部分是應用層加密的資料傳輸(HTTPS 資料)。可以看到,整個握手過程設計, server 與 client 互動的過程大致有一下幾個部分:
-
client hello
-
server hello
-
client key exchange
-
change cipher spec
概括起來,前兩個過程 生成了非對稱加密的重要引數,建立了非對稱金鑰。後兩個過程 交換了非對稱加密的公鑰。
欲詳細瞭解握手的過程是如何發生的,交換了哪些內容,請參考我的另一篇部落格:
從 TCP 建立連線到 HTTPS接收到第一個資料包,到底發生了什麼?[1]
接下來讓我們通過簡單的抓包我們可以看到整個 SSL 協議握手與建立會話的全過程。
wireshark是我最喜歡的抓包工具之一,另一個是 tcpdump。為了生動形象更易閱讀,我們用wireshark來抓包瞭解整個過程。
-
client hello (client)
-
server hello + certificate exchange (server)
-
change cipher spec + finished (client)
-
change cipher sped + fihished (server)
通過抓包,往往我們可以清楚地看到從建立 TCP 連線到 TCP 連線斷開,整個 SSL 握手的過程。這個過程是艱辛複雜的,從握手開始到真正的加密資料傳送,之間有多個 RoundTrip。因此我們的優化過程就是從這裡開始入手。
SSL 流量優化
順著這樣一個思路,先來看下優化到底能從哪些地方入手:
-
優化 SSL握手之前過程
-
TCP 優化
-
-
優化 SSL握手過程
-
False start
-
優化握手流程
-
加快金鑰計算
-
-
優化 SSL握手之後過程
-
HTTP/2 || SPDY
-
內容壓縮
-
對稱加密套件選擇
-
TCP Fast Open(一下簡稱 TFO)目的在於簡化 TCP 握手的過程,通過一定的協商過程(SYN 攜帶 cookie 資訊)使得下一次握手的時候在 SYN 包中就可以攜帶資料,同時 Server 可以在發出 SYN ACK 之後立即開始傳送資料。因此如果我們的 SSL 握手建立 TCP 連線的時候能夠啟用 TFO,那麼我們的 SSL 握手流量就可以減少 至少一個 RTT。
但是,由於 Server linux 核心過於低,有的核心並沒有支援 TFO。這點在我編譯 nginx 的時候就遇到了服務端核心過低的問題。
TCP_FASTOPEN 特性在 kernel-3.6 被客戶端支援,在 kernel-3.7 被服務端支援。
想要開啟 TFO,先要檢查下你的核心是否支援。
TCP 引數調優
大家都知道 TCP 的幾個重要的演算法,這裡要說的就是 TCP 的慢啟動(Slow-Start)演算法。
由於存在慢啟動的特徵,開始時的 TCP 視窗可能較為小,因此如果我們的 SSL 握手包非常的大,那麼潛在的可能就是發生 TCP 分片,相當於增加了一個額外的 RTT。因此,如果能在不影響其他服務的情況下,調整視窗的大小,也是一種優化的思路。
關於一點,我們在後面還會繼續說明。
優化 SSL 握手的過程
優化 SSL 握手過程是整個過程的核心,這裡也是重點優化的方向。我們有以下幾個思考優化的點:
-
TLS False Start
-
HSTS
-
Session Resumption
-
OCSP Stapling
-
證書優化
-
非對稱加密運算加速
在 SSL 握手完成之前率先開始加密的 application 資料互動。這種思想很像剛剛提及的TFO思路。通過節省掉一次互動過程從而節省 RTT。實現的基礎是 client 拿到server radom 之後(server hello)其實就已經有了生成非對稱加密所需要的金鑰的 3 個關鍵:
server random,client random, pre-master-secret。
未開啟 False Start
開啟了 False Start
通過抓包可以發現,在剛收到 437 號包協商還沒結束的時候,應用層的資料已經開始了傳輸 (439-442 號包),而到 443號包才完成了握手。
在現代的 SSL False Start 實現中,主要有兩種方式:
-
NPN:Next Protocol Negotiation 協議文件[2]
NPN 最早引入 false start 思想,是 SSL 的擴充套件部分。不過 Chrome 51 中移除將會 NPN,因此應該儘快支援 ALPN來替代
-
ALPN Application Layer Protocol Negotiation
ALPN 是 Google 根據 HTTP2 設計的,計劃替代 npn 的新協議,目的與 npn 相同,通過協商,優化 SSL 握手的過程。然而,ALPN 需要 OpenSSL 1.0.2支援,當前主流伺服器作業系統基本都沒有內建這個版本。所以需要進行升級。
這兩者本身設計用於協議協商的,因此也可以用於支援 HTTP2。但是他們之間有一些差異:
-
NPN 是服務端傳送所支援的 HTTP 協議列表,由客戶端選擇
-
NPN 的協商結果是在 Change Cipher Spec之後加密傳送給服務端
-
ALPN 是客戶端傳送所支援的 HTTP 協議列表,由服務端選擇
-
ALPN 的協商結果是通過 Server Hello 明文發給客戶端
這兩個協議用於 SSL False Start 本身就是使用了副作用。不過,為了開啟 False Start,加密套件必須支援前向保密,例如 ECDHE_RSA加密套件。
HTTP Strict Transport Security
HTTP Strict Transport Security(簡稱HSTS)是一個安全功能,告訴瀏覽器只能通過 HTTPS 訪問當前資源,禁止 HTTP 方式。最初目的防範降級攻擊(Downgrade attack)或中間人攻擊(Man-in-the-middle attack)
我們肯定遇到過這樣的情況:訪問一個 HTTP 開頭的網站,然後瀏覽器自動幫我們跳轉到了HTTPS 頁面。這意味著一次潛在的重定向。
HSTS 通過內建的 preload list 儲存了一份可以定期更新的列表,對於列表中的域名,即使使用者之前沒有訪問過,也會使用 HTTPS 協議。如果你的服務端開啟了 HSTS 這個選項,那麼客戶端可以直接訪問 HTTPS 頁面,從而避免了一次無謂的跳轉,其實也是加速了訪問的過程。
這樣做的好處是帶來安全性的同時,避免了潛在的重定向帶來的一次額外 RTT。
然而全站 HTTPS + HSTS,導致一旦更換回 HTTP,處於 list 的老使用者會被無限重定向。
chrome 檢視本地 hsts preload list 的方式是訪問 chrome://net-internals/#hsts
SSL Session Resumption
這裡使我們優化大點的重點。Session Resumption意為會話恢復。顧名思義,這裡的會話正是指恢復先前的 SSL 會話。通過會話恢復技術我們可以以最小的代價,規避最耗時的握手過程。收益可觀。
我們先上抓包:
通過抓包可以看到,開啟了 Session Resumption 的 SSL 握手,在 2 個 RTT 的時候就已經完成了握手。這是如何做到的呢?
我們先來看3個名詞概念:
-
Session ID:會話 ID,用於唯一標識一個 SSL 會話。
-
Session Ticket:會話憑據,記錄了本次會話相關的金鑰與認證資訊。
-
Session Cache:會話快取,用於快取會話相關的資訊。
目前實現會話恢復大致有 2 種方式:Session ID 恢復會話和 Session Ticket 恢復會話。
Session ID 方式
TLS 握手中生成的 Session ID,服務端可以將 Session ID 和 協商後的資訊對應存起來。同時,瀏覽器也可以儲存 Session ID,並在後續的 ClientHello 握手中帶上它,如果服務端能找到與之匹配的資訊,就可以完成一次快速握手。
這樣做帶來的弊端也很明顯:
-
由於目前大多是大型網際網路結構。負載均衡中,多機之間往往沒有同步 Session 資訊,如果客戶端兩次請求沒有落在同一臺機器上就無法找到匹配的資訊,導致失敗。
-
服務端儲存 Session ID 對應的資訊不好控制失效時間,太短起不到作用,太長又佔用服務端大量資源。
為了解決這個問題,可以通過 IP hash 來負載,但是依然容易因為網路環境變化導致握手的失敗。通過 Session cache 共享也是一種解決方案,nginx 提供了本機的共享,但是跨節點的共享就需要通過一箇中心化的 Session cache 來實現。這無疑增加了跟多的成本。
Session Ticket 方式
Session Ticket 是通過服務端安全金鑰加密過的會話資訊,最終儲存在 client。瀏覽器如果在 ClientHello 時帶上了 Session Ticket,只要伺服器能成功解密就可以完成快速握手。這種方式緩解了 Session ID 帶來的潛在的握手失敗的風險。
然而,Session Ticket 的加密使得 必須保證所有 server 上的加密金鑰相同否則負載均衡到不同機器的時候因為無法解密一樣會導致失敗。
看似,Session Ticket 更容易解決 Session id 的一些痛點。然而在我們的抓包中,很容易發現,因為 Session Ticket 資訊過大,導致 TCP 分片。特別是在開始階段門限值小,更容易出現這個問題。
OCSP Stapling
OCSP stapling 是 OCSP 的一個擴充套件協議,那麼什麼是 OCSP 呢?
在 SSL 握手階段,客戶端驗證證書有效狀態通常有兩個方式:
-
CRL(Certificate Revocation List,證書撤銷名單)
-
OCSP(Online Certificate Status Protocol,線上證書狀態協議)
CRL 時效性差,而且 list 會越來越大,瀏覽器更新通常不及時(以天為單位)。
OCSP 是為了解決舊的證書檢查協議的弊端的實時協議,實時解析證書的有效性。
然而,某些客戶端會在 SSL 握手階段進一步協商時,實時查詢 OCSP 介面,並在獲得結果前阻塞後續流程,這對效能影響很大。OCSP stapling 正是為了解決這些問題:將對於證書的驗證過程代理到 server上,減少 client 的壓力。查詢後打包下發client。同時 OCSP stapling 支援 Certificate Transparency,證書安全性有保障。
通過 OpenSSL 內建工具可以查詢服務端 OCSP Stalping 的狀態:
openssl s_client -connect varycloud.com:443 -servername varycloud.com -status -tlsextdebug < /dev/null 2>&1 | grep -i "OCSP response"OCSP response:OCSP Response Data: OCSP Response Status: successful (0x0) Response Type: Basic OCSP Response
同一個請求,第一次可能沒有開啟 OCSP Stapling,原因在於 server 會先去獲取OCSP 的狀態,這需要一個過程。
證書優化
證書優化是指減小不必要的握手開銷,這裡我們先不討論加密運算的消耗。通常我們有一個證書的原則,證書包含中間證書但是不包含根證書,這是最小化的證書配置,因為根證書瀏覽器一般都會內建。而中間證書如果不一同下發,那麼瀏覽器會遞迴的查詢請求中間證書,這也是無意義的消耗。
通常來說,我們常用的非對稱加密演算法是 RSA 。可以說,RSA也是我們資訊保安的基石。然而RSA的金鑰長度是非常巨大的,計算量也是驚人的。
一種更新的解決方案是使用 DH 加密。這也是一種非對稱加密。對於DH 加密,常常與橢圓曲線一起使用被稱為 ECDH。通過橢圓曲線優化之後的的 DH 加密有更好的效能同時佔用更小的金鑰長度。
ECDH 演算法使用較短的金鑰即可達到相同程度的安全性,這是因為它依賴橢圓曲線而不是對數曲線。ECC 是建立在基於橢圓曲線的離散對數問題上的密碼體制,給定橢圓曲線上的一個點 P,一個整數 k,求解 Q = kP 很容易;給定一個點 P、Q,知道 Q = kP,求整數 k 確是一個難題。
OpenSSL 1.0.2 採用了 Intel 最新的優化成果。這個優化特性只是在 1.0.1L 之後才加入的,下圖表格中 OpenSSL 的 1.0.1e 版本 ecdh(nistp256) 的效能只有 2548,而 OpenSSL 的 1.1.0b 版本 ecdh(nistp256) 效能能達到 10271,提高了 4 倍。
非對稱加密運算加速
RSA 和 ECDHERSA 為什麼會消耗CPU資源?
RSA 主要是對客戶端發回來 pre_master_secret 進行解密,它消耗 CPU 資源的過程是私鑰解密的計算;而 ECDHERSA 則有兩個步驟:
-
生成 ECC 橢圓曲線的公鑰和幾個重要的引數;
-
對這幾個引數進行簽名,客戶端要確保引數是服務端發過來的,就是通過 RSA 的簽名來保證。
RSA 簽名為什麼消耗 CPU 呢?RSA 簽名同樣有兩個步驟:
-
首先它通過 SHA1 進行 Hash 計算;
-
對 Hash 結果進行私鑰加密,也就是最終消耗 CPU 的過程是私鑰解密和私鑰加密的計算。這兩個計算為什麼消耗 CPU?看下圖公式,如果 e 或者 d 這個指數是一個接近 2 的 2048 次方的天文數字,那就非常消耗 CPU。這也就是 RSA 演算法為什麼消耗 CPU 的最直接的數學解釋。
對於非對稱加密的優化,常見的方式有
-
硬體加速卡
-
GPU 加速叢集代理計算
專門的硬體優化可以節約大量的時間,這點相信瞭解計算機原理的同學都能明白。同樣 GPU 代理計算是將計算與握手分離,同樣是專用叢集專門加速,因此效率更高。
因為對這些具體的硬體沒有專門的測試和了解,就不過多的討論。
優化 SSL 握手之後的流程
在通過了握手之後,我們的流量進入了安全的通訊通道。此時還有可以優化的空間麼?
-
對稱加密演算法優化
-
HTTP2 / SPDY (TCP 多路複用)
經過了 SSL 握手之後的流量就進入了加密的通訊通道,因此對稱加密演算法的效率其實也是十分重要的。然而因為大多數的對稱加密演算法並沒有很大的優化空間,這裡的優化往往得不到足夠的重視。我們這裡以一種特殊的加密演算法為例進行介紹。
ChaCha20-Poly1305 是 Google 所採用的一種新式加密演算法,效能強大,在 CPU 為精簡指令集的 ARM 平臺上尤為顯著(ARM v8前效果較明顯),在同等配置的手機中表現是 AES 的 4 倍。可減少加密解密所產生的資料量進而可以改善使用者體驗,減少等待時間,節省電池壽命等。
ARM v8 之後加入了 AES 指令。所以在這些平臺上的裝置,AES 方式反而比 chacha20 方式更快,效能更好。因此作為一個可選的方案,chacha20 可能對於一些老舊的機型(向嵌入式大牛諮詢過之後瞭解到,目前來說還有很多在使用 arm v7 的硬體)來說更具優勢。
SPDY || HTTP/2
對於流量的優化還有一種思路,那就是 TCPSSL 連結的複用。講到http的多路複用,就繞不開 HTTP/2。這次,我們從HTTP/2的始祖 SPDY 說起。
傳統的http請求模型類似於 1:1 形式,單個 TCP(SSL) 連結服務一個 HTTP 請求,很多情況下,TCP 連結資源並沒有得到充分的利用。HTTP 多路複用能夠有效地提高 TCP 鏈路的效率。
SPDY 是 Google 開發的基於 TCP 的傳輸層協議,用以最小化網路延遲,提升網路速度,優化使用者的網路使用體驗。IETF 對谷歌提出的 SPDY 協議進行了標準化,於 2015 年 5 推出了類似於 SPDY 協議的 HTTP 2.0 協議標準(簡稱HTTP/2)。谷歌因此宣佈放棄對 SPDY 協議的支援,轉而支援 HTTP/2。
簡而言之,無論是 SPDY 還是 HTTP/2 目的都在於提高連結的效率。而且,HTTP/2 天然支援 SSL false start 和 HTTPS,安全性和效率上都提供了保障。
HTTPS 優化實踐
這裡我們將通過資料的對比來檢驗 SSL 優化的效果。我們從以下 3 個測試結果來分析 SSL 優化的效果。
-
SSL Session Resumption
-
OCSP stapling
-
加密演算法優化與分析
採用壓力測試的方式,對比不同配置下,相同壓力服務端的服務能力和延遲表現。
服務端配置
-
CPU Info E5 6 核心 12 執行緒(超執行緒)
-
32G RAM
-
Intel Corporation 82574L Gigabit Network 千兆網絡卡
-
Linux kernel
-
2.6.32-573.el6.x86_64
-
-
nginx
-
version: nginx/1.10.2
-
-
OpenSSL
-
OpenSSL 1.0.2l
-
-
壓測工具
-
wrk 是一個專門用於測試 HTTP 請求響應的工具。功能強大,但是對於 HTTPS 支援不夠友好。
-
wrk-go 是我使用 go 和自己的測試框架 perfm 重新實現的一個測試工具,專門增加了 HTTPS 相關的選項,方便測試。
-
文後有 wrk-go 連結,歡迎大家一起完善它!(這不是廣告 ^ ^) [3]
-
測試結果
未開啟會話恢復 ./wrk -c 24 -t 24 -d 3m -H "Connection: Close" https://varycloud.comRunning 3m test @ https://varycloud.com24 threads and 24 connectionsThread Stats Avg Stdev Max +/- Stdev Latency 4.76ms 8.34ms 174.25ms 97.01% Req/Sec 51.91 10.48 90.00 69.50%223479 requests in 3.00m, 67.99MB readNon-2xx or 3xx responses: 223479Requests/sec: 1240.95Transfer/sec: 386.58KB 開啟會話恢復 ./wrk -c 24 -t 24 -d 3m -H "Connection: Close" https://varycloud.comRunning 3m test @ https://varycloud.com24 threads and 24 connectionsThread Stats Avg Stdev Max +/- Stdev Latency 1.71ms 10.17ms 165.85ms 98.78% Req/Sec 360.06 53.23 545.00 82.35%1544216 requests in 3.00m, 469.78MB readNon-2xx or 3xx responses: 1544216Requests/sec: 8574.25Transfer/sec: 2.61MB
通過對比可以看到,SSL 會話恢復如果能夠被成功複用,那麼提升非常可觀。相比基準資料能夠提升大約 64%。
同時由於略過了最消耗 CPU 的運算過程,可以看到系統的負載明顯降低,同時 IO 明顯提升,這說明服務能力得到了提高。
綜上可以得出,Session Resumption效果是非常明顯的。
OCSP Stapling 測試分析
OCSP Stapling 是我們感興趣的另一個特性。下面給出開啟 OCSP Stapling 的表現測試資料。具體統計了平均耗時 avg,耗時差異 diff,資料包大小 Data Length。
上圖中白框圈中的部分為開啟 OCSP後握手步驟的差別及耗時相差最多的階段。 <---表示當前握手步驟和箭頭指向的握手步驟是在同一個資料包。
分析
然而測試結十分令我們困惑:
理論上講,OCSP Sapling 規避了一次客戶端與服務端之間的通訊,時間消耗上應該會有提升。然而卻出現了下降。通過分析我們發現了端倪。
Client與兩者的對比,在經歷各自2000請求,發現與開啟OCSP Stapling的Server通訊,統計上多消耗了10.347ms左右, 最大的差異是Client收到Certificate後再到Client Key Exchange,開啟OCSP Stapling的Server的通訊多消耗了5.370ms。
對比兩者握手差異,對應任務多了兩部分
-
新增加傳輸Certificate Status的資料包,包大小為619 bytes。
-
驗證Certificate Status的合法性
由於網路傳輸耗時與所傳輸的資料量多少成正比,以到 server ping 的耗時為標準時間的話,從時間上推斷傳輸Certificate加上Certificate Status耗時大概約為16.1848
11.488 * (1514 + 619) / 1514 = 16.1848
多出來的大概 0.673ms 可認為是驗證 Certificate Status 合法性的時間(如果不考慮兩臺伺服器在通訊時微小的網路差異的話)。
驗證內容包括 3.2 Signed Response Acceptance Requirements[4]:
-
驗證證書一致
在OCSP Response 中所指的證書和請求中所指證書一致。
-
驗證簽名有效
OCSP Response 中的簽名有效。
-
簽名者身份合法
-
簽名者的身份和相映應接受請求者匹配。
-
簽名者正被授權簽名回覆。
-
-
驗證時間