TLS協議
一,TLS協議的組成
TLS協議架構模塊分為兩層:TLS記錄協議,TLS握手協議
① TLS記錄協議: 是所有子協議的基層,規定了TLS收發數據的基本單位。所有子協議都需要通過記錄協議發出,多個記錄數據可以在一個TCP中發送。主要負責使用對稱密碼對消息進行加密。
② TLS握手協議:
- 握手協議:客戶端和服務端在握手過程中協商 TLS版本號,隨機數,密碼套件,交換證書,最終獲取會話密鑰。
- 密碼規格變更協議:發送一個通知,告知對方后續的通信采用加密保護。
- 警告協議:向對方發出警告,類似于http中的狀態碼。不支持舊版本,證書異常都會發送警報,另一方收到警報后可以進行相應的處理
- 應用數據協議:將TLS承載的應用數據傳達給通信對象
二,TLS的握手過程
上圖是TLS握手的一個簡易流程,每一個框就是一個 TLS Record(記錄),多個Record合并成一個TCP來進行發送,可以看到整個TLS握手只需要2次消息往返即2RTT就能完成 ( TLS 1.2是2RTT,TLS 1.3可以做到 1RTT甚至0RTT )
接下來我們來結合wireshark抓包來詳細介紹每一步的流程:
1,ClientHello
首先客戶端先向服務端發起握手請求,在這個請求過程中client會攜帶以下參數
- TLS 版本: 確定使用1.2還是1.3 但是這個地方固定是1.2。之后我們會在1.2與1.3的兼容性中聊到
- Client提供的隨機數: 用于后續生成會話密鑰
- Cipher Suites: 可用密碼套件列表,供服務端選擇
- 會話ID(可忽略): 可用于之后TLS1.3的優化 —— 會話緩存
- 可用壓縮方式
密碼套件
客戶端和服務端在使用TLS建立連接時需要選擇一組恰當的加密算法來實現安全通信,這些算法的組合就被稱為 密碼套件
如圖中的密碼套件那一行:TLS_ECDHE_RSA_WITH_AES_128-CBC-SHA
他的意思是
- 握手時使用ECDHE算法進行密鑰交換
- 用RSA簽名和身份認證
- 握手后的通信使用 AES對稱算法,密鑰長度 128 位
- 分組模式為 CBC
- 摘要算法為 SHA 用于消息認證和產生隨機數
2,ServerHello
服務端接收到客戶端的握手請求,會回復客戶端同時 發送 ServerHello,ServerHello會根據之前的ClientHello的參數來做如下操作:
- **確認 TLS版本 **
- 服務器提供的隨機數
- 選擇的密碼套件
3,Server Certificate
數字證書 與 CA
① CA證書:
我們知道 在 https中 加密采用了非對稱加密,通過私鑰加密,公鑰解密。私鑰由雙方生成并維護。但是公鑰不能任意生成,公鑰涉及到一個公鑰信任問題,誰都能發布公鑰的話,黑客就可以偽造公鑰來進行攻擊。所以說公鑰得由一個公認可信的第三方生成,讓他作為信任的起點,遞歸的終點,構建信任鏈,這個第三方就是 CA(證書認證機構)。② CA證書鏈:
CA可以簽發三種證書,他們的區別在于可信程度,按照可信程度從高到低來排序即為:EV, OV, DV。
CA如果想證明自己,可以找上級CA為自己簽發證書,不斷向上直到 Root CA。Root CA會給自己簽發自簽名證書或者根證書,這個證書是必須相信的。如下圖所示即為數字證書CA的信任鏈
③ 證書弱點:
證書的簽發可能失誤或者被欺騙,簽發了錯誤的證書,這個時候可以通過 CRL(證書吊銷列表) 和 OCSP(在線證書狀態協議)來及時對問題證書進行廢止
服務端會將自己的證書鏈發送給客戶端,交給客戶端進行驗證。值得注意的不是只發送一個證書,前文有說到 二級CA的證明需要一級CA的證明,驗證二級證書的簽名需要一級證書的公鑰來進行解密,所以需要返回一整個證書鏈(無需攜帶根證書,用戶瀏覽器自帶了根證書)
4,Server Key Exchange
以 ECDHE算法為例,服務端生成一個 公鑰 名為Server Params,用來實現密鑰交換算法,再用私鑰進行簽名認證
Handshake Protocol: Server Key ExchangeEC Diffie-Hellman Server ParamsCurve Type: named_curve (0x03)Named Curve: x25519 (0x001d)Pubkey: 3b39deaf00217894e...Signature Algorithm: rsa_pkcs1_sha512 (0x0601)Signature: 37141adac38ea4...
5,Client Key Exchange
客戶端首先會先對證書鏈進行驗證,再根據服務端所選的算法以及Server Params來生成對應的Client Params返回給服務端
Handshake Protocol: Client Key ExchangeEC Diffie-Hellman Client ParamsPubkey: 8c674d0e08dc27b5eaa…
此時服務端和客戶端擁有了 Client Params,Server Params,Client Random,Server Random,服務端會先使用Params用ECDHE算法生成一個Pre-Master,再用 Random + Pre-Master 生成主密鑰 Master Secret。
master_secret = PRF(pre_master_secret, "master secret",ClientHello.random + ServerHello.random)// 之所以需要使用三個隨機數來生成主密鑰,是因為TLS的設計者并不信任服務端和客戶端生成的隨機數的可靠性。所以將三個不可靠的隨機數混合起來來提升隨機數的隨機程度。
有了主密鑰后并不會直接進行使用,而是通過PRF算法擴展出更多的派送會話密鑰,來用作每次會話的獨立密鑰。這樣即使主密鑰泄露已傳輸的數據仍然安全,因為每次會話的密鑰是獨立的具備前向安全性。
6,Change Chiper Spec & Finished
通信轉換為使用會話密鑰 + 對稱算法進行加密通信,并且握手結束。
由于使用的是ECDHE,客戶端可以無需等待服務端返回 Finished就直接發出HTTP報文,省去等待一個消息返回的時間浪費,這個叫做
TLS False Start
圖示總結(以ECDHE握手為例)
三,TLS 1.3
TLS 1.3 相比于 TLS 1.2有了以下幾項巨大的提示:兼容,性能,安全
1,兼容性
在TLS 1.2時期,版本號是記錄在Version字段的,即Version:TLS 1.2(0x0303)。我們可能理所應當的認為TLS 1.3就是Version:TLS 1.3(0X0304)。但是這樣肯定不行,這樣會導致大量只認老協議的代理服務器,網關都無法處理請求,導致握手失敗。
如果要進行區分1.2 和 1.3就要通過新引入的協議 擴展協議,在當前的記錄后添加一列拓展字段,老版本因為不認識則直接舍棄,新版本則可以對其進行識別處理,這便實現了向后兼容
所以 TLS 1.3 還是保留 Version:TLS 1.2(0x0303),只不過在 擴展協議 中新加了有一個 #supported_version擴展,在這個擴展中標記了TLS的版本號
Handshake Protocol: Client HelloVersion: TLS 1.2 (0x0303)Extension: supported_versions (len=11)Supported Version: TLS 1.3 (0x0304)Supported Version: TLS 1.2 (0x0303)
2,性能提升
TLS 1.3 相比于 TLS 1.2最大性能提升就是 原先TLS1.2握手需要 2RTT,現在TLS1.3握手只需 1RTT,性能整整提升了一倍。
優化的點如下:
1,TLS 1.3舍棄了之前的繁瑣的協商方式,在第一次Client Hello時 通過擴展組就將簽名算法,客戶端公鑰參數(Client Params)等等內容全部提供了服務端。同時服務端會在Server Hello時就攜帶好各種參數以及ChangeCipherSpec。
通過這兩點 成功將握手的報文發送提升至 1RTT
3,安全性(待補充)
廢棄了一些分組和加密算法,由于沒具體學加密算法,學了之后再補充
密碼套件大大減少,得益于密碼套件的減少,在Client Hello階段可以直接生成公鑰參數以及曲線
四, TLS 1.3 握手過程
1,Client Hello
Handshake Protocol: Client HelloVersion: TLS 1.2 (0x0303)Random: cebeb6c05403654d66c2329…Cipher Suites (18 suites)Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)Extension: supported_versions (len=9)Supported Version: TLS 1.3 (0x0304)Supported Version: TLS 1.2 (0x0303)Extension: supported_groups (len=14)Supported Groups (6 groups)Supported Group: x25519 (0x001d)Supported Group: secp256r1 (0x0017)Extension: key_share (len=107)Key Share extensionClient Key Share Length: 105Key Share Entry: Group: x25519Key Share Entry: Group: secp256r1
TLS 1.3與TLS 1.2相比而言,新增了三個拓展項
- supported_version:這個我們之前有提到,TLS 1.3需要使用該拓展項來進行版本區分
- supported_group:支持的曲線
- key_share:曲線對應的參數
可以看到相比于1.2,1.3再hello的時候就將自身支持的參數發送給了服務端
2,Server Hello
收到客戶端的消息后,此時服務端就擁有了 Client Random 和 Server Random 、Client Params 和 Server Params,可以直接通過ECDHE算出 Pre-Master。
此時通過 Certificate Verify一個安全增強措施,用私鑰將曲線,套件,參數等數據進行數字簽名,強化了身份認證和防篡改。
可以發現 生成會話密鑰以及進入加密通信的時間要比TLS 1.2更加的早。TLS 1.2還需要進行一次繁瑣的 Key Exchange,而1.3在兩次Hello中就已經完成了Key的交換
五,HTTPS的優化
HTTPS在HTTP握手的基礎上加上了一個TLS握手,所以他的連接速度肯定也會有所下降,有時甚至會比HTTP慢上幾秒(遠古時期),這下子 HTTPS的已經刻板印象的變成 “Slow” 了。那本章我們來分析 HTTPS究竟慢在哪以及如何優化。
首先HTTPS的加密分為了兩部分 TLS握手前的非對稱加密 和 TLS握手后的對稱加密
**對稱加密:**目前TLS支持的對稱加密算法性能很好 再加上硬件優化,這塊的性能損耗很小可以忽略不計
那我們就可以把 [慢] 聚集于 TLS握手期間,我們可以將銷毀總結為以下幾點:
- 握手所需的額外報文傳輸
- 驗證證書時 需要訪問 CA 獲取 CRL或OCSP
- 非對稱加密處理 Pre-Master
- 產生用于密鑰交換的臨時公私鑰對
1,加解密優化:
① SSL加速卡 or SSL加速服務器: 使用專門的加速卡來分擔CPU的計算壓力 或者 使用 SSL加速服務器集群 [卸載] TLS握手時的加解密計算,提升加解密的速度
② 協議優化:
選擇更快更好的密碼套件,會使加解密,密鑰交換過程中更加快速 例如:
性能更高的曲線:x25519
對稱加密算法:AES_128 會比 AES_256 更好一點
密鑰交換協議:ECDHE運算更快,安全性高,支持False Start,將握手消息往返由 2-RTT減少至 1-RTT
這些都可以在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;
2,證書優化:
在TLS 1.2中我們有說到,服務器需要將自己的證書鏈發送給客戶端。那這里 證書的傳輸 以及 證書的驗證 就是性能損耗的關鍵點。
證書的傳輸: 在傳輸的方面,我們需要減小證書的大小,這樣可以節約帶寬以及減小計算量。那我們可以選擇ECDHE橢圓曲線的證書,ECDHE證書(224位)相比于RSA證書(2048位)小了很多。
證書的優化: 證書在驗證過程中不僅要面對繁瑣的解密,如果證書撤銷,客戶端還需要訪問CA,下載CRL或者OSCP的數據,產生DNS查詢,連接建立,收發數據等一系列網絡通信。
那我們可以選擇將 CRL和OSCP保存起來,這樣就無需進行額外的網絡查詢。
- **CRL(不推薦):**CRL是定期發布的,這樣會存在時間窗口的安全隱患,而且CRL中存放的是所有被吊銷的證書,這個列表只會不斷變大,動輒 幾MB 保存CRL是不可取的。
- **OCSP Stapling:**這是OSCP的一個新補丁,原先的OCSP是在線證書狀態協議,他也需要查詢CA然后獲取證書狀態。但是OCSP Stapling 我們可以讓服務器預先訪問CA獲取OCSP的響應,在握手的過程中將證書和OCSP查詢的狀態在握手時一起返回給客戶端,這樣就無需進行額外的查詢。
3,會話復用
在客戶端和服務器首次連接后會各自保存一個Session ID,并且為當前會話生成一個主密鑰Master。那我們能不能將主密鑰Master生成這個過程跳過了,這樣就跳過了證書驗證和密鑰交換。答案是 有的,兄弟有的
① 緩存SessionID(1RTT): 在第一次連接后,服務器內存保存SessionId以及當前會話的主密鑰,當客戶端再次連接時發送一個ID過來,就從緩存中尋找,如果找到對應的主密鑰則恢復會話,并且跳過證書驗證和密鑰交換。
② Session Ticket(1RTT): 第一種是由服務器緩存會話ID,但是對于千萬級連接的服務器,這樣的內存開銷是十分大的。所以這個時候我們可以做一個緩存對象轉換.
1,第一次訪問時服務端,使用有效期的ticket密鑰對Session簽發的Ticket,客戶端接收到后緩存Ticket。
2,客戶端下一次建立連接時使用拓展 session_ticket發送 Ticket。
3,服務端獲取Ticket,并進行解密校驗是否有效,是否超時。如果正常則恢復會話并進行加密通信。
③ 預共享密鑰(0RTT): 在TLS1.3下,可以通過該方法達到 0RTT,在Ticket的基礎上同時攜帶上應用數據(Early Data),這種方式叫做Pre-shared Key,檢簡稱**PSK **
這樣可以免去服務器確認的步驟,實現0-RTT。