字節(jié)二面:優(yōu)化HTTPS的手段,你知道幾個?
由裸數(shù)據(jù)傳輸?shù)?HTTP 協(xié)議轉(zhuǎn)成加密數(shù)據(jù)傳輸?shù)?HTTPS 協(xié)議,給應(yīng)用數(shù)據(jù)套了個「保護(hù)傘」,提高安全性的同時也帶來了性能消耗。
因?yàn)?HTTPS 相比 HTTP 協(xié)議多一個 TLS 協(xié)議握手過程,目的是為了通過非對稱加密握手協(xié)商或者交換出對稱加密密鑰,這個過程最長可以花費(fèi)掉 2 RTT,接著后續(xù)傳輸?shù)膽?yīng)用數(shù)據(jù)都得使用對稱加密密鑰來加密/解密。
為了數(shù)據(jù)的安全性,我們不得不使用 HTTPS 協(xié)議,至今大部分網(wǎng)址都已從 HTTP 遷移至 HTTPS 協(xié)議,因此針對 HTTPS 的優(yōu)化是非常重要的。
這次,就從多個角度來優(yōu)化 HTTPS。
分析性能損耗既然要對 HTTPS 優(yōu)化,那得清楚哪些步驟會產(chǎn)生性能消耗,再對癥下藥。
產(chǎn)生性能消耗的兩個環(huán)節(jié):
- 第一個環(huán)節(jié), TLS 協(xié)議握手過程;
- 第二個環(huán)節(jié),握手后的對稱加密報文傳輸。
對于第二環(huán)節(jié),現(xiàn)在主流的對稱加密算法 AES、ChaCha20 性能都是不錯的,而且一些 CPU 廠商還針對它們做了硬件級別的優(yōu)化,因此這個環(huán)節(jié)的性能消耗可以說非常地小。
而第一個環(huán)節(jié),TLS 協(xié)議握手過程不僅增加了網(wǎng)絡(luò)延時(最長可以花費(fèi)掉 2 RTT),而且握手過程中的一些步驟也會產(chǎn)生性能損耗,比如:
- 對于 ECDHE 密鑰協(xié)商算法,握手過程中會客戶端和服務(wù)端都需要臨時生成橢圓曲線公私鑰;
- 客戶端驗(yàn)證證書時,會訪問 CA 獲取 CRL 或者 OCSP,目的是驗(yàn)證服務(wù)器的證書是否有被吊銷;
- 雙方計(jì)算 Pre-Master,也就是會話密鑰。
為了大家更清楚這些步驟在 TLS 協(xié)議握手的哪一個階段,我畫出了這幅圖:
硬件優(yōu)化
玩游戲時,如果我們怎么都戰(zhàn)勝不了對方,那么有一個最有效、最快的方式來變強(qiáng),那就是「充錢」,如果還是不行,那說明你充的錢還不夠多。
對于計(jì)算機(jī)里也是一樣,軟件都是跑在物理硬件上,硬件越牛逼,軟件跑的也越快,所以如果要優(yōu)化 HTTPS 優(yōu)化,最直接的方式就是花錢買性能參數(shù)更牛逼的硬件。
但是花錢也要花對方向,HTTPS 協(xié)議是計(jì)算密集型,而不是 I/O 密集型,所以不能把錢花在網(wǎng)卡、硬盤等地方,應(yīng)該花在 CPU 上。
一個好的 CPU,可以提高計(jì)算性能,因?yàn)?HTTPS 連接過程中就有大量需要計(jì)算密鑰的過程,所以這樣可以加速 TLS 握手過程。
另外,如果可以,應(yīng)該選擇可以支持 AES-NI 特性的 CPU,因?yàn)檫@種款式的 CPU 能在指令級別優(yōu)化了 AES 算法,這樣便加速了數(shù)據(jù)的加解密傳輸過程。
如果你的服務(wù)器是 Linux 系統(tǒng),那么你可以使用下面這行命令查看 CPU 是否支持 AES-NI 指令集:
如果我們的 CPU 支持 AES-NI 特性,那么對于對稱加密的算法應(yīng)該選擇 AES 算法。否則可以選擇 ChaCha20 對稱加密算法,因?yàn)? ChaCha20 算法的運(yùn)算指令相比 AES 算法會對 CPU 更友好一點(diǎn)。
軟件優(yōu)化
如果公司預(yù)算充足對于新的服務(wù)器是可以考慮購買更好的 CPU,但是對于已經(jīng)在使用的服務(wù)器,硬件優(yōu)化的方式可能就不太適合了,于是就要從軟件的方向來優(yōu)化了。
軟件的優(yōu)化方向可以分層兩種,一個是軟件升級,一個是協(xié)議優(yōu)化。
先說第一個軟件升級,軟件升級就是將正在使用的軟件升級到最新版本,因?yàn)樽钚掳姹静粌H提供了最新的特性,也優(yōu)化了以前軟件的問題或性能。比如:
- 將 Linux 內(nèi)核從 2.x 升級到 4.x;
- 將 OpenSSL 從 1.0.1 升級到 1.1.1;
- …
看似簡單的軟件升級,對于有成百上千服務(wù)器的公司來說,軟件升級也跟硬件升級同樣是一個棘手的問題,因?yàn)橐獙?shí)行軟件升級,會花費(fèi)時間和人力,同時也存在一定的風(fēng)險,也可能會影響正常的線上服務(wù)。
既然如此,我們把目光放到協(xié)議優(yōu)化,也就是在現(xiàn)有的環(huán)節(jié)下,通過較小的改動,來進(jìn)行優(yōu)化。
協(xié)議優(yōu)化
協(xié)議的優(yōu)化就是對「密鑰交換過程」進(jìn)行優(yōu)化。
(1) 密鑰交換算法優(yōu)化
TLS 1.2 版本如果使用的是 RSA 密鑰交換算法,那么需要 4 次握手,也就是要花費(fèi) 2 RTT,才可以進(jìn)行應(yīng)用數(shù)據(jù)的傳輸,而且 RSA 密鑰交換算法不具備前向安全性。
總之使用 RSA 密鑰交換算法的 TLS 握手過程,不僅慢,而且安全性也不高。
因此如果可以,盡量選用 ECDHE 密鑰交換算法替換 RSA 算法,因?yàn)樵撍惴ㄓ捎谥С帧窮alse Start」,它是“搶跑”的意思,客戶端可以在 TLS 協(xié)議的第 3 次握手后,第 4 次握手前,發(fā)送加密的應(yīng)用數(shù)據(jù),以此將 TLS 握手的消息往返由 2 RTT 減少到 1 RTT,而且安全性也高,具備前向安全性。
ECDHE 算法是基于橢圓曲線實(shí)現(xiàn)的,不同的橢圓曲線性能也不同,應(yīng)該盡量選擇 x25519 曲線,該曲線是目前最快的橢圓曲線。
比如在 Nginx 上,可以使用 ssl_ecdh_curve 指令配置想使用的橢圓曲線,把優(yōu)先使用的放在前面:
對于對稱加密算法方面,如果對安全性不是特別高的要求,可以選用 AES_128_GCM,它比 AES_256_GCM 快一些,因?yàn)槊荑€的長度短一些。
比如在 Nginx 上,可以使用 ssl_ciphers 指令配置想使用的非對稱加密算法和對稱加密算法,也就是密鑰套件,而且把性能最快最安全的算法放在最前面:
(2) TLS 升級
當(dāng)然,如果可以,直接把 TLS 1.2 升級成 TLS 1.3,TLS 1.3 大幅度簡化了握手的步驟,完成 TLS 握手只要 1 RTT,而且安全性更高。
在 TLS 1.2 的握手中,一般是需要 4 次握手,先要通過 Client Hello (第 1 次握手)和 Server Hello(第 2 次握手) 消息協(xié)商出后續(xù)使用的加密算法,再互相交換公鑰(第 3 和 第 4 次握手),然后計(jì)算出最終的會話密鑰,下圖的左邊部分就是 TLS 1.2 的握手過程:
上圖的右邊部分就是 TLS 1.3 的握手過程,可以發(fā)現(xiàn) TLS 1.3 把 Hello 和公鑰交換這兩個消息合并成了一個消息,于是這樣就減少到只需 1 RTT 就能完成 TLS 握手。
怎么合并的呢?具體的做法是,客戶端在 Client Hello 消息里帶上了支持的橢圓曲線,以及這些橢圓曲線對應(yīng)的公鑰。
服務(wù)端收到后,選定一個橢圓曲線等參數(shù),然后返回消息時,帶上服務(wù)端這邊的公鑰。經(jīng)過這 1 個 RTT,雙方手上已經(jīng)有生成會話密鑰的材料了,于是客戶端計(jì)算出會話密鑰,就可以進(jìn)行應(yīng)用數(shù)據(jù)的加密傳輸了。
而且,TLS1.3 對密碼套件進(jìn)行“減肥”了,對于密鑰交換算法,廢除了不支持前向安全性的 RSA 和 DH 算法,只支持 ECDHE 算法。
對于對稱加密和簽名算法,只支持目前最安全的幾個密碼套件,比如 openssl 中僅支持下面 5 種密碼套件:
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_128_GCM_SHA256
- TLS_AES_128_CCM_8_SHA256
- TLS_AES_128_CCM_SHA256
之所以 TLS1.3 僅支持這么少的密碼套件,是因?yàn)?TLS1.2 由于支持各種古老且不安全的密碼套件,中間人可以利用降級攻擊,偽造客戶端的 Client Hello 消息,替換客戶端支持的密碼套件為一些不安全的密碼套件,使得服務(wù)器被迫使用這個密碼套件進(jìn)行 HTTPS 連接,從而破解密文。
證書優(yōu)化
為了驗(yàn)證的服務(wù)器的身份,服務(wù)器會在 TSL 握手過程中,把自己的證書發(fā)給客戶端,以此證明自己身份是可信的。
對于證書的優(yōu)化,可以有兩個方向:
- 一個是證書傳輸
- 一個是證書驗(yàn)證
1. 證書傳輸優(yōu)化
要讓證書更便于傳輸,那必然是減少證書的大小,這樣可以節(jié)約帶寬,也能減少客戶端的運(yùn)算量。所以,對于服務(wù)器的證書應(yīng)該選擇 橢圓曲線(ECDSA)證書,而不是 RSA 證書,因?yàn)樵谙嗤踩珡?qiáng)度下, ECC 密鑰長度比 RSA 短的多。
2. 證書驗(yàn)證優(yōu)化
客戶端在驗(yàn)證證書時,是個復(fù)雜的過程,會走證書鏈逐級驗(yàn)證,驗(yàn)證的過程不僅需要「用 CA 公鑰解密證書」以及「用簽名算法驗(yàn)證證書的完整性」,而且為了知道證書是否被 CA 吊銷,客戶端有時還會再去訪問 CA, 下載 CRL 或者 OCSP 數(shù)據(jù),以此確認(rèn)證書的有效性。
這個訪問過程是 HTTP 訪問,因此又會產(chǎn)生一系列網(wǎng)絡(luò)通信的開銷,如 DNS 查詢、建立連接、收發(fā)數(shù)據(jù)等。
(1) CRL
CRL 稱為證書吊銷列表(Certificate Revocation List),這個列表是由 CA 定期更新,列表內(nèi)容都是被撤銷信任的證書序號,如果服務(wù)器的證書在此列表,就認(rèn)為證書已經(jīng)失效,不在的話,則認(rèn)為證書是有效的。
但是 CRL 存在兩個問題:
- 第一個問題,由于 CRL 列表是由 CA 維護(hù)的,定期更新,如果一個證書剛被吊銷后,客戶端在更新 CRL 之前還是會信任這個證書,實(shí)時性較差;
- 第二個問題,隨著吊銷證書的增多,列表會越來越大,下載的速度就會越慢,下載完客戶端還得遍歷這么大的列表,那么就會導(dǎo)致客戶端在校驗(yàn)證書這一環(huán)節(jié)的延時很大,進(jìn)而拖慢了 HTTPS 連接。
(2) OCSP
因此,現(xiàn)在基本都是使用 OCSP ,名為在線證書狀態(tài)協(xié)議(Online Certificate Status Protocol)來查詢證書的有效性,它的工作方式是向 CA 發(fā)送查詢請求,讓 CA 返回證書的有效狀態(tài)。
不必像 CRL 方式客戶端需要下載大大的列表,還要從列表查詢,同時因?yàn)榭梢詫?shí)時查詢每一張證書的有效性,解決了 CRL 的實(shí)時性問題。
OCSP 需要向 CA 查詢,因此也是要發(fā)生網(wǎng)絡(luò)請求,而且還得看 CA 服務(wù)器的“臉色”,如果網(wǎng)絡(luò)狀態(tài)不好,或者 CA 服務(wù)器繁忙,也會導(dǎo)致客戶端在校驗(yàn)證書這一環(huán)節(jié)的延時變大。
(3) OCSP Stapling
于是為了解決這一個網(wǎng)絡(luò)開銷,就出現(xiàn)了 OCSP Stapling,其原理是:服務(wù)器向 CA 周期性地查詢證書狀態(tài),獲得一個帶有時間戳和簽名的響應(yīng)結(jié)果并緩存它。
當(dāng)有客戶端發(fā)起連接請求時,服務(wù)器會把這個「響應(yīng)結(jié)果」在 TLS 握手過程中發(fā)給客戶端。由于有簽名的存在,服務(wù)器無法篡改,因此客戶端就能得知證書是否已被吊銷了,這樣客戶端就不需要再去查詢。
會話復(fù)用
TLS 握手的目的就是為了協(xié)商出會話密鑰,也就是對稱加密密鑰,那我們?nèi)绻覀儼咽状?TLS 握手協(xié)商的對稱加密密鑰緩存起來,待下次需要建立 HTTPS 連接時,直接「復(fù)用」這個密鑰,不就減少 TLS 握手的性能損耗了嗎?
這種方式就是會話復(fù)用(TLS session resumption),會話復(fù)用分兩種:
- 第一種叫 Session ID
- 第二種叫 Session Ticket
(1) Session ID
Session ID 的工作原理是,客戶端和服務(wù)器首次 TLS 握手連接后,雙方會在內(nèi)存緩存會話密鑰,并用唯一的 Session ID 來標(biāo)識,Session ID 和會話密鑰相當(dāng)于 key-value 的關(guān)系。
當(dāng)客戶端再次連接時,hello 消息里會帶上 Session ID,服務(wù)器收到后就會從內(nèi)存找,如果找到就直接用該會話密鑰恢復(fù)會話狀態(tài),跳過其余的過程,只用一個消息往返就可以建立安全通信。當(dāng)然為了安全性,內(nèi)存中的會話密鑰會定期失效。
但是它有兩個缺點(diǎn):
- 服務(wù)器必須保持每一個客戶端的會話密鑰,隨著客戶端的增多,服務(wù)器的內(nèi)存壓力也會越大。
- 現(xiàn)在網(wǎng)站服務(wù)一般是由多臺服務(wù)器通過負(fù)載均衡提供服務(wù)的,客戶端再次連接不一定會命中上次訪問過的服務(wù)器,于是還要走完整的 TLS 握手過程。
(2) Session Ticket
為了解決 Session ID 的問題,就出現(xiàn)了 Session Ticket,服務(wù)器不再緩存每個客戶端的會話密鑰,而是把緩存的工作交給了客戶端,類似于 HTTP 的 Cookie。
客戶端與服務(wù)器首次建立連接時,服務(wù)器會加密「會話密鑰」作為 Ticket 發(fā)給客戶端,交給客戶端緩存該 Ticket。
客戶端再次連接服務(wù)器時,客戶端會發(fā)送 Ticket,服務(wù)器解密后就可以獲取上一次的會話密鑰,然后驗(yàn)證有效期,如果沒問題,就可以恢復(fù)會話了,開始加密通信。
對于集群服務(wù)器的話,要確保每臺服務(wù)器加密 「會話密鑰」的密鑰是一致的,這樣客戶端攜帶 Ticket 訪問任意一臺服務(wù)器時,都能恢復(fù)會話。
Session ID 和 Session Ticket 都不具備前向安全性,因?yàn)橐坏┘用堋笗捗荑€」的密鑰被破解或者服務(wù)器泄漏「會話密鑰」,前面劫持的通信密文都會被破解。
同時應(yīng)對重放攻擊也很困難,這里簡單介紹下重放攻擊工作的原理。
假設(shè) Alice 想向 Bob 證明自己的身份。Bob 要求 Alice 的密碼作為身份證明,愛麗絲應(yīng)盡全力提供(可能是在經(jīng)過如哈希函數(shù)的轉(zhuǎn)換之后)。與此同時,Eve 竊聽了對話并保留了密碼(或哈希)。
交換結(jié)束后,Eve(冒充 Alice )連接到 Bob。當(dāng)被要求提供身份證明時,Eve 發(fā)送從 Bob 接受的最后一個會話中讀取的 Alice 的密碼(或哈希),從而授予 Eve 訪問權(quán)限。
重放攻擊的危險之處在于,如果中間人截獲了某個客戶端的 Session ID 或 Session Ticket 以及 POST 報文,而一般 POST 請求會改變數(shù)據(jù)庫的數(shù)據(jù),中間人就可以利用此截獲的報文,不斷向服務(wù)器發(fā)送該報文,這樣就會導(dǎo)致數(shù)據(jù)庫的數(shù)據(jù)被中間人改變了,而客戶是不知情的。
避免重放攻擊的方式就是需要對會話密鑰設(shè)定一個合理的過期時間。
(3) Pre-shared Key
前面的 Session ID 和 Session Ticket 方式都需要在 1 RTT 才能恢復(fù)會話。
而 TLS1.3 更為牛逼,對于重連 TLS1.3 只需要 0 RTT,原理和 Ticket 類似,只不過在重連時,客戶端會把 Ticket 和 HTTP 請求一同發(fā)送給服務(wù)端,這種方式叫 Pre-shared Key。
同樣的,Pre-shared Key 也有重放攻擊的危險。
如上圖,假設(shè)中間人通過某種方式,截獲了客戶端使用會話重用技術(shù)的 POST 請求,通常 POST 請求是會改變數(shù)據(jù)庫的數(shù)據(jù),然后中間人就可以把截獲的這個報文發(fā)送給服務(wù)器,服務(wù)器收到后,也認(rèn)為是合法的,于是就恢復(fù)會話,致使數(shù)據(jù)庫的數(shù)據(jù)又被更改,但是此時用戶是不知情的。
所以,應(yīng)對重放攻擊可以給會話密鑰設(shè)定一個合理的過期時間,以及只針對安全的 HTTP 請求如 GET/HEAD 使用會話重用。
總結(jié)
對于硬件優(yōu)化的方向,因?yàn)?HTTPS 是屬于計(jì)算密集型,應(yīng)該選擇計(jì)算力更強(qiáng)的 CPU,而且最好選擇支持 AES-NI 特性的 CPU,這個特性可以在硬件級別優(yōu)化 AES 對稱加密算法,加快應(yīng)用數(shù)據(jù)的加解密。
對于軟件優(yōu)化的方向,如果可以,把軟件升級成較新的版本,比如將 Linux 內(nèi)核 2.X 升級成 4.X,將 openssl 1.0.1 升級到 1.1.1,因?yàn)樾掳姹镜能浖粌H會提供新的特性,而且還會修復(fù)老版本的問題。
對于協(xié)議優(yōu)化的方向:
- 密鑰交換算法應(yīng)該選擇 ECDHE 算法,而不用 RSA 算法,因?yàn)?ECDHE 算法具備前向安全性,而且客戶端可以在第三次握手之后,就發(fā)送加密應(yīng)用數(shù)據(jù),節(jié)省了 1 RTT。
- 將 TSL1.2 升級 TSL1.3,因?yàn)?TSL1.3 的握手過程只需要 1 RTT,而且安全性更強(qiáng)。
對于證書優(yōu)化的方向:
- 服務(wù)器應(yīng)該選用 ECDSA 證書,而非 RSA 證書,因?yàn)樵谙嗤踩墑e下,ECC 的密鑰長度比 RSA 短很多,這樣可以提高證書傳輸?shù)男?
- 服務(wù)器應(yīng)該開啟 OCSP Stapling 功能,由服務(wù)器預(yù)先獲得 OCSP 的響應(yīng),并把響應(yīng)結(jié)果緩存起來,這樣 TLS 握手的時候就不用再訪問 CA 服務(wù)器,減少了網(wǎng)絡(luò)通信的開銷,提高了證書驗(yàn)證的效率;
對于重連 HTTPS 時,我們可以使用一些技術(shù)讓客戶端和服務(wù)端使用上一次 HTTPS 連接使用的會話密鑰,直接恢復(fù)會話,而不用再重新走完整的 TLS 握手過程。
常見的會話重用技術(shù)有 Session ID 和 Session Ticket,用了會話重用技術(shù),當(dāng)再次重連 HTTPS 時,只需要 1 RTT 就可以恢復(fù)會話。對于 TLS1.3 使用 Pre-shared Key 會話重用技術(shù),只需要 0 RTT 就可以恢復(fù)會話。
這些會話重用技術(shù)雖然好用,但是存在一定的安全風(fēng)險,它們不僅不具備前向安全,而且有重放攻擊的風(fēng)險,所以應(yīng)當(dāng)對會話密鑰設(shè)定一個合理的過期時間。