說明《透視HTTP協議》是 羅劍鋒 (奇虎360技術專家)在極客時間開的一門專欄課,筆者記錄一下學習筆記,僅供參考。
上一講中我講了 TLS1.2 的握手過程,你是不是已經完全掌握了呢?
不過 TLS1.2 已經是 10 年前(2008 年)的“老”協議了,雖然歷經考驗,但畢竟“歲月不饒人”,在安全、性能等方面已經跟不上如今的互聯網了。
於是經過四年、近 30 個草案的反復打磨,TLS1.3 終於在去年(2018 年)“粉墨登場”,再次確立了信息安全領域的新標准。
在抓包分析握手之前,我們先來快速瀏覽一下 TLS1.3 的三個主要改進目標:兼容、安全與性能。
最大化兼容性
由於 1.1、1.2 等協議已經出現了很多年,很多應用軟件、中間代理(官方稱為“MiddleBox”)只認老的記錄協議格式,更新改造很困難,甚至是不可行(設備僵化)。
在早期的試驗中發現,一旦變更了記錄頭字段里的版本號,也就是由 0x303(TLS1.2)改為 0x304(TLS1.3)的話,大量的代理服務器、網關都無法正確處理,最終導致 TLS 握手失敗。
為了保證這些被廣泛部署的“老設備”能夠繼續使用,避免新協議帶來的“沖擊”,TLS1.3 不得不做出妥協,保持現有的記錄格式不變,通過“偽裝”來實現兼容,使得 TLS1.3 看上去“像是”TLS1.2。
那么,該怎么區分 1.2 和 1.3 呢?
這要用到一個新的擴展協議(Extension Protocol),它有點“補充條款”的意思,通過在記錄末尾添加一系列的“擴展字段”來增加新的功能,老版本的 TLS 不認識它可以直接忽略,這就實現了“后向兼容”。
在記錄頭的 Version 字段被兼容性“固定”的情況下,只要是 TLS1.3 協議,握手的“Hello”消息后面就必須有“supported_versions”擴展,它標記了 TLS 的版本號,使用它就能區分新舊協議。
其實上一講 Chrome 在握手時發的就是 TLS1.3 協議,你可以看一下“Client Hello”消息后面的擴展,只是因為服務器不支持 1.3,所以就“后向兼容”降級成了 1.2。
Handshake Protocol: Client Hello |
|
Version: TLS 1.2 (0x0303) |
|
Extension: supported_versions (len=11) |
|
Supported Version: TLS 1.3 (0x0304) |
|
Supported Version: TLS 1.2 (0x0303) |
復制代碼
TLS1.3 利用擴展實現了許多重要的功能,比如“supported_groups”“key_share”“signature_algorithms”“server_name”等,這些等后面用到的時候再說。
強化安全
TLS1.2 在十來年的應用中獲得了許多寶貴的經驗,陸續發現了很多的漏洞和加密算法的弱點,所以 TLS1.3 就在協議里修補了這些不安全因素。
比如:
偽隨機數函數由 PRF 升級為 HKDF(HMAC-based Extract-and-Expand Key Derivation Function);
明確禁止在記錄協議里使用壓縮;
廢除了 RC4、DES 對稱加密算法;
廢除了 ECB、CBC 等傳統分組模式;
廢除了 MD5、SHA1、SHA-224 摘要算法;
廢除了 RSA、DH 密鑰交換算法和許多命名曲線。
經過這一番“減肥瘦身”之后,TLS1.3 里只保留了 AES、ChaCha20 對稱加密算法,分組模式只能用 AEAD 的 GCM、CCM 和 Poly1305,摘要算法只能用 SHA256、SHA384,密鑰交換算法只有 ECDHE 和 DHE,橢圓曲線也被“砍”到只剩 P-256 和 x25519 等 5 種。
減肥可以讓人變得更輕巧靈活,TLS 也是這樣。
算法精簡后帶來了一個意料之中的好處:原來眾多的算法、參數組合導致密碼套件非常復雜,難以選擇,而現在的 TLS1.3 里只有 5 個套件,無論是客戶端還是服務器都不會再犯“選擇困難症”了。
這里還要特別說一下廢除 RSA 和 DH 密鑰交換算法的原因。
上一講用 Wireshark 抓包時你一定看到了,瀏覽器默認會使用 ECDHE 而不是 RSA 做密鑰交換,這是因為它不具有“前向安全”(Forward Secrecy)。
假設有這么一個很有耐心的黑客,一直在長期收集混合加密系統收發的所有報文。如果加密系統使用服務器證書里的 RSA 做密鑰交換,一旦私鑰泄露或被破解(使用社會工程學或者巨型計算機),那么黑客就能夠使用私鑰解密出之前所有報文的“Pre-Master”,再算出會話密鑰,破解所有密文。
這就是所謂的“今日截獲,明日破解”。
而 ECDHE 算法在每次握手時都會生成一對臨時的公鑰和私鑰,每次通信的密鑰對都是不同的,也就是“一次一密”,即使黑客花大力氣破解了這一次的會話密鑰,也只是這次通信被攻擊,之前的歷史消息不會受到影響,仍然是安全的。
所以現在主流的服務器和瀏覽器在握手階段都已經不再使用 RSA,改用 ECDHE,而 TLS1.3 在協議里明確廢除 RSA 和 DH 則在標准層面保證了“前向安全”。
提升性能
HTTPS 建立連接時除了要做 TCP 握手,還要做 TLS 握手,在 1.2 中會多花兩個消息往返(2-RTT),可能導致幾十毫秒甚至上百毫秒的延遲,在移動網絡中延遲還會更嚴重。
現在因為密碼套件大幅度簡化,也就沒有必要再像以前那樣走復雜的協商流程了。TLS1.3 壓縮了以前的“Hello”協商過程,刪除了“Key Exchange”消息,把握手時間減少到了“1-RTT”,效率提高了一倍。
那么它是怎么做的呢?
其實具體的做法還是利用了擴展。客戶端在“Client Hello”消息里直接用“supported_groups”帶上支持的曲線,比如 P-256、x25519,用“key_share”帶上曲線對應的客戶端公鑰參數,用“signature_algorithms”帶上簽名算法。
服務器收到后在這些擴展里選定一個曲線和參數,再用“key_share”擴展返回服務器這邊的公鑰參數,就實現了雙方的密鑰交換,后面的流程就和 1.2 基本一樣了。
我為 1.3 的握手過程畫了一張圖,你可以對比 1.2 看看區別在哪里。
除了標准的“1-RTT”握手,TLS1.3 還引入了“0-RTT”握手,用“pre_shared_key”和“early_data”擴展,在 TCP 連接后立即就建立安全連接發送加密消息,不過這需要有一些前提條件,今天暫且不說。
握手分析
目前 Nginx 等 Web 服務器都能夠很好地支持 TLS1.3,但要求底層的 OpenSSL 必須是 1.1.1,而我們實驗環境里用的 OpenSSL 是 1.1.0,所以暫時無法直接測試 TLS1.3。
不過我在 Linux 上用 OpenSSL1.1.1 編譯了一個支持 TLS1.3 的 Nginx,用 Wireshark 抓包存到了 GitHub 上,用它就可以分析 TLS1.3 的握手過程。
在 TCP 建立連接之后,瀏覽器首先還是發一個“Client Hello”。
因為 1.3 的消息兼容 1.2,所以開頭的版本號、支持的密碼套件和隨機數(Client Random)結構都是一樣的(不過這時的隨機數是 32 個字節)。
Handshake Protocol: Client Hello |
|
Version: 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 extension |
|
Client Key Share Length: 105 |
|
Key Share Entry: Group: x25519 |
|
Key Share Entry: Group: secp256r1 |
復制代碼
注意“Client Hello”里的擴展,“supported_versions”表示這是 TLS1.3,“supported_groups”是支持的曲線,“key_share”是曲線對應的參數。
這就好像是說:
“還是照老規矩打招呼,這邊有這些這些信息。但我猜你可能會升級,所以再多給你一些東西,也許后面用的上,咱們有話盡量一口氣說完。”
服務器收到“Client Hello”同樣返回“Server Hello”消息,還是要給出一個隨機數(Server Random)和選定密碼套件。
Handshake Protocol: Server Hello |
|
Version: TLS 1.2 (0x0303) |
|
Random: 12d2bce6568b063d3dee2… |
|
Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301) |
|
Extension: supported_versions (len=2) |
|
Supported Version: TLS 1.3 (0x0304) |
|
Extension: key_share (len=36) |
|
Key Share extension |
|
Key Share Entry: Group: x25519, Key Exchange length: 32 |
復制代碼
表面上看和 TLS1.2 是一樣的,重點是后面的擴展。“supported_versions”里確認使用的是 TLS1.3,然后在“key_share”擴展帶上曲線和對應的公鑰參數。
服務器的“Hello”消息大概是這個意思:
“還真讓你給猜對了,雖然還是按老規矩打招呼,但咱們來個‘舊瓶裝新酒’。剛才你給的我都用上了,我再給幾個你缺的參數,這次加密就這么定了。”
這時只交換了兩條消息,客戶端和服務器就拿到了四個共享信息:Client Random和Server Random、Client Params和Server Params,兩邊就可以各自用 ECDHE 算出“Pre-Master”,再用 HKDF 生成主密鑰“Master Secret”,效率比 TLS1.2 提高了一大截。
在算出主密鑰后,服務器立刻發出“Change Cipher Spec”消息,比 TLS1.2 提早進入加密通信,后面的證書等就都是加密的了,減少了握手時的明文信息泄露。
這里 TLS1.3 還有一個安全強化措施,多了個“Certificate Verify”消息,用服務器的私鑰把前面的曲線、套件、參數等握手數據加了簽名,作用和“Finished”消息差不多。但由於是私鑰簽名,所以強化了身份認證和和防竄改。
這兩個“Hello”消息之后,客戶端驗證服務器證書,再發“Finished”消息,就正式完成了握手,開始收發 HTTP 報文。
雖然我們的實驗環境暫時不能抓包測試 TLS1.3,但互聯網上很多網站都已經支持了 TLS1.3,比如Nginx、GitHub,你可以課后自己用 Wireshark 試試。
在 Chrome 的開發者工具里,可以看到這些網站的 TLS1.3 應用情況。
小結
今天我們一起學習了 TLS1.3 的新特性,用抓包研究了它的握手過程,不過 TLS1.3 里的內容很多,還有一些特性沒有談到,后面會繼續講。
為了兼容 1.1、1.2 等“老”協議,TLS1.3 會“偽裝”成 TLS1.2,新特性在“擴展”里實現;
1.1、1.2 在實踐中發現了很多安全隱患,所以 TLS1.3 大幅度刪減了加密算法,只保留了 ECDHE、AES、ChaCha20、SHA-2 等極少數算法,強化了安全;
TLS1.3 也簡化了握手過程,完全握手只需要一個消息往返,提升了性能。
課下作業
TLS1.3 里的密碼套件沒有指定密鑰交換算法和簽名算法,那么在握手的時候會不會有問題呢?
結合上一講的 RSA 握手過程,解釋一下為什么 RSA 密鑰交換不具有“前向安全”。
TLS1.3 的握手過程與 TLS1.2 的“False Start”有什么異同?