視頻內(nèi)容類的業(yè)務(wù)對(duì)延遲比較敏感,而在海外運(yùn)營(yíng)場(chǎng)景下,這種延遲敏感更為突出,愛(ài)奇藝海外版項(xiàng)目在初期技術(shù)要求就是秒開(kāi),除了 CDN、邊緣節(jié)點(diǎn)的部署外,海外后端團(tuán)更是對(duì) HTTPS 的請(qǐng)求做過(guò)一系列的優(yōu)化,現(xiàn)分享之前工作中的一些技術(shù)性探索和優(yōu)化。
為什么要在移動(dòng)端花費(fèi)這么多精力?
移動(dòng)端因?yàn)樵O(shè)備性質(zhì)以及網(wǎng)絡(luò)環(huán)境因素等,導(dǎo)致新鏈接、以及鏈接復(fù)用問(wèn)題放大。
HTTPS 請(qǐng)求花費(fèi)的時(shí)間?
一個(gè)全新的 HTTPS 鏈接,從發(fā)起請(qǐng)求到數(shù)據(jù)返回經(jīng)過(guò)幾個(gè) RTT?假設(shè)沒(méi)有任何緩存,一個(gè) HTTPS 請(qǐng)求得經(jīng)過(guò) 10 個(gè) RTT 才能返回內(nèi)容。
一個(gè) RTT 如果是 50ms,這個(gè)全新的請(qǐng)求至少花費(fèi) 50*10=500ms。這還沒(méi)有算后端業(yè)務(wù)處理的時(shí)間。HTTPS 請(qǐng)求延遲確實(shí)比較高。
通常情況下業(yè)務(wù) HTTP 的延遲容忍度較差,Server To Client 的模式,效率總歸是越高越好。
現(xiàn)實(shí)情況會(huì)有各種緩存,減少不必要的消耗,所以很少發(fā)生上面這種極端的 10 個(gè) RTT 情況。
DNS 有本地緩存或者用了 HTTPDNS 預(yù)解析,第1步可以省掉。
如果瀏覽器聲明了 HSTS ,可以省略 302 轉(zhuǎn)向,第3步可以省掉。
如果本地已經(jīng)有主流的 CA DNS 緩存 第6步可以省掉
如果 CA 本地有驗(yàn)證緩存或者啟用 OCSP Stapling 的本地驗(yàn)證 7、8步可以省掉
在有各類緩存情況下,一個(gè)常規(guī)的 HTTPS 請(qǐng)求會(huì)保留 4 個(gè) RTT 流程。
除了 HTTPS 內(nèi)容請(qǐng)求外,握手的階段或者 TLS 層是否還可以再繼續(xù)優(yōu)化一下,讓我們的 APP 或者視頻播放再快那么一點(diǎn)?答案:肯定是的!
使用 WireShark 抓包, 根據(jù) TCP 包,我們畫(huà)了下面流程圖,一步步分析流程,并確定中間可優(yōu)化的點(diǎn)。(TLS1.2 協(xié)議 ECDHE 算法)
整個(gè)流程有以下幾個(gè)階段:
TLS Hello 確定協(xié)議版本、密碼套件、對(duì)稱密鑰隨機(jī)數(shù)
Server Certificate 服務(wù)器發(fā)送證書(shū)鏈
Client/Server Key Exchange (DH 算法,協(xié)商交換加密算法)
ChangeCipher Spec 對(duì)稱加密雙向校驗(yàn)
TLS Hello階段的分析與應(yīng)用
先看看 Clienthello 的幾個(gè)主要的參數(shù)
struct
hello{
Version// TLS版本號(hào)
Random// 客戶端隨機(jī)數(shù)
Sessionid
CipherSuites // 客戶端支持的加密套件
Extension:support version; // 擴(kuò)展TLS版本支持
}
上面的抓包 Version 是 TLS1.2,擴(kuò)展中帶有 TLS1.3。如果服務(wù)端支持 1.3 ,將改為 1.3 協(xié)議。
服務(wù)端先要進(jìn)行對(duì)應(yīng)的支持配置,如下。
ssl_protocols TLSv1.2 TLSv1.3
Cipher Suites 是個(gè)數(shù)組,會(huì)包含新舊一堆加密算法支持,優(yōu)先級(jí)從上到下。
服務(wù)端 Server Hello 返回 Cipher Suite,根據(jù) Client 的請(qǐng)求匹配一個(gè)合適的加密套件。如下服務(wù)器返回支持的套件。
Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA256
上面什么意思?握手時(shí)對(duì)稱加密參數(shù)交換使用 RSA。通信加密使用 256 位長(zhǎng)度對(duì)稱 AES 算法,GCM 和 SHA256 是 AES 的分組模式和摘要算法。
這個(gè)階段優(yōu)化的幾個(gè)思路:
密鑰交換算法改進(jìn):RSA 可以改為 DH 類算法(Diffie-Hellman),如 ECDHE。在同等復(fù)雜度下,計(jì)算效率更高,證書(shū)體積也更小。
優(yōu)化 ECDHE 算法實(shí)現(xiàn),優(yōu)先選擇效率最高的橢圓曲線實(shí)現(xiàn) x25519。在 Nginx 后臺(tái)使用 ssl_ecdh_curve 配置橢圓曲線優(yōu)先級(jí)。
ssl_ecdh_curveX25519:secp384r1;
同時(shí) ECDHE 支持 False Start。
啟用了 False Start,在 Client 發(fā)送完 ChangeCipher Spec 就可以發(fā)送加密的應(yīng)用數(shù)據(jù),減少1個(gè) RTT 等待時(shí)間。
服務(wù)端配置 Cipher Suites 的優(yōu)先級(jí)。把性能最高的算法放在前面。
ssl_prefer_server_cipherson;
ssl_ciphers EECDH+ECDSA+AES128+SHA:RSA+AES128+SHA...
盡管 AES 的效率很高,但在一些安全性沒(méi)那么高的業(yè)務(wù)下,AES 密鑰的長(zhǎng)度可以小一些,256位改為128位。
Server Certificate階段
Certificate 階段會(huì)將自身公鑰和證書(shū)CA中間公鑰發(fā)給客戶端。本地驗(yàn)證證書(shū)合法后繼續(xù) Client Key Exchange 流程。在這個(gè)階段,有兩個(gè)方向可以優(yōu)化:證書(shū)傳輸+證書(shū)驗(yàn)證。
證書(shū)傳輸
證書(shū)的大小當(dāng)然越小越好,在生成證書(shū)階段選擇 ECDSA,而不是 RSA 證書(shū),安全不變的條件下,密鑰長(zhǎng)度更小,運(yùn)算量也更小。
比如用類似下面生成 ECDSA 證書(shū)
openssl ecparam -genkey -name prime256v1 -out key.pem
證書(shū)驗(yàn)證
客戶端在驗(yàn)證證書(shū)時(shí),會(huì)走證書(shū)鏈逐級(jí)驗(yàn)證,而且為了知道證書(shū)是否被 CA 吊銷,客戶端會(huì)訪問(wèn) CA 下載 OCSP 數(shù)據(jù),確認(rèn)證書(shū)是否有效。
OCSP 需要向 CA 查詢,因此也是要發(fā)生網(wǎng)絡(luò)請(qǐng)求,如果CA服務(wù)器的延遲過(guò)大,會(huì)導(dǎo)致客戶端在校驗(yàn)證書(shū)這一環(huán)節(jié)的延時(shí)變大。
OCSP Stapling
為了解決證書(shū)有效性驗(yàn)證的問(wèn)題,出現(xiàn)了 OCSP Stapling。
服務(wù)器向 CA 周期性地查詢證書(shū)狀態(tài),獲得一個(gè)帶有時(shí)間戳和簽名的響應(yīng)并緩存,當(dāng)客戶端來(lái)請(qǐng)求證書(shū),在 TLS 握手階段,服務(wù)器將該結(jié)果給客戶端。
因?yàn)榻Y(jié)果帶有 CA 私鑰的簽名,所有結(jié)果可信,客戶端在本地就可以判斷證書(shū)的有效性。
在Nginx中開(kāi)啟OCSP Stapling
ssl_staplingon;
ssl_stapling_verifyon;
ssl_trusted_certificate xx.pem
使用以下命令測(cè)試服務(wù)器是否開(kāi)啟 ocspstaping
openssl s_client -connect ip:443 -status
OCSP response:no response sent //出現(xiàn)以下 則沒(méi)配置
密鑰交換和驗(yàn)證的階段
Client/ServerKey Exchange 階段
在這一階段主要是交換 DH 加密公鑰,如選定橢圓曲線,生成橢圓曲線公鑰,公鑰簽名,與客戶端的 ClientKey Exchange 呼應(yīng),最終獲取對(duì)方的公鑰,用來(lái)加密 AES 的密鑰。
加解密雙方驗(yàn)證
Change Cipher Spec/Encrypted Handshake Message
在 Key Change 階段,加密的參數(shù)均已生成,雙方有了交換密鑰的公鑰,也有對(duì)稱密鑰的參數(shù)?梢赃M(jìn)行數(shù)據(jù)傳輸了
如果雙方都驗(yàn)證加密和解密沒(méi)問(wèn)題,那么握手正式完成。于是,就可以正常收發(fā)加密的 HTTP 請(qǐng)求和響應(yīng)了。
以上兩個(gè)階段是否可以優(yōu)化?是的,我們可以采用鏈接復(fù)用的方式跳過(guò)該階段。
鏈接復(fù)用優(yōu)化
復(fù)用有兩個(gè)方式:SessionID 和SessionTicket,實(shí)現(xiàn)不同但目標(biāo)一致。(一個(gè)服務(wù)端實(shí)現(xiàn),一個(gè)客戶端實(shí)現(xiàn))
客戶端和服務(wù)器首次 TLS 握手連接后,雙方會(huì)在內(nèi)存緩存會(huì)話密鑰,并用唯一的 Session ID 來(lái)標(biāo)識(shí)。在下一次鏈接時(shí),服務(wù)端可以知道一個(gè)進(jìn)來(lái)的連接是否在之前已經(jīng)建立過(guò)連接,如果在服務(wù)器中也存在這個(gè) session 的 key,那么它就能重用。
在服務(wù)端開(kāi)啟 SessionID
ssl_session_cacheshared:SSL:50m;
ssl_session_timeout 1d;
為了解決 Session ID 的問(wèn)題,就出現(xiàn)了 Session Ticket,服務(wù)器不再緩存每個(gè)客戶端的會(huì)話密鑰,而是把緩存的工作交給了客戶端,類似于 HTTP 的 Cookie。
客戶端與服務(wù)器首次建立連接時(shí),服務(wù)器會(huì)加密「會(huì)話密鑰」作為 Ticket 發(fā)給客戶端,交給客戶端緩存該 Ticket。
客戶端再次連接服務(wù)器時(shí),客戶端會(huì)發(fā)送 Ticket,服務(wù)器解密后就可以獲取上一次的會(huì)話密鑰,然后驗(yàn)證有效期,如果沒(méi)問(wèn)題,就可以恢復(fù)會(huì)話了。
在服務(wù)端開(kāi)啟 Session ticket
ssl_session_ticketson;ssl_session_ticket_key xx.key;
用了鏈接重用技術(shù),當(dāng)再次重連 HTTPS 時(shí),只需要 1 RTT 就可以恢復(fù)會(huì)話。對(duì)于 TLS1.3 使用 Pre-shared Key 重用技術(shù),可以實(shí)現(xiàn) 0RTT 恢復(fù)鏈接。
采用鏈接重用,當(dāng)然不可避免的產(chǎn)生重放攻擊風(fēng)險(xiǎn)。
對(duì)于安全性要求非常高的業(yè)務(wù)要慎重的使用鏈接重用, 當(dāng)然如果衡量好重用過(guò)期時(shí)間,同時(shí)業(yè)務(wù)端做好安全防范,獲得的收益也不會(huì)小。