Google、Facebook等均開始支援的HTTP3到底是個什麼鬼?

語言: CN / TW / HK

GitHub 19k Star 的Java工程師成神之路,不來了解一下嗎!

最近一段時間以來,關於HTTP/3的新聞有很多,越來越多的國際大公司已經開始使用HTTP/3了。

-w649
-w659

所以,HTTP/3已經是箭在弦上了,全面使用只是個時間問題,那麼,作為一線開發者,我們也是時候瞭解下到底什麼是HTTP/3,為什麼需要HTTP/3了。

那麼,本文就來講解一下到底什麼是HTTP/3?他用到了哪些技術?解決了什麼問題?

HTTP/2 存在的問題

在撰寫本文之前,我專門寫了一篇文章《HTTP/2做錯了什麼?剛剛輝煌2年就要被棄用了!?》分析HTTP/2存在的問題以及背後的原因。

這裡就不詳細介紹了,強烈建議大家先閱讀下這篇文章,有助於本文的學習。

在上一篇文章中我們提到過HTTP/2因為底層使用的傳輸層協議仍然是TCP,所以他存在著TCP隊頭阻塞、TCP握手延時長以及協議僵化等問題。

這導致HTTP/2雖然使用了多路複用、二進位制分幀等技術,但是仍然存在著優化空間。

QUIC協議

我們知道,HTTP/2之所以"被棄用",是因為他使用的傳輸層協議仍然是TCP,所以HTTP/3首要解決的問題就是繞開TCP。

那麼如果研發一種新的協議,同樣還是會因為受到中間裝置僵化的影響,導致無法被大規模應用。所以,研發人員們想到了一種基於UDP實現的方式。

於是,Google是最先採用這種方式並付諸於實踐的,他們在2013年推出了一種叫做QUIC的協議,全稱是Quick UDP Internet Connections。

從名字中可以看出來,這是一種完全基於UDP的協議。

在設計之初,Google就希望使用這個協議來取代HTTPS/HTTP協議,使網頁傳輸速度加快。2015年6月,QUIC的網路草案被正式提交至網際網路工程任務組。2018 年 10 月,網際網路工程任務組 HTTP 及 QUIC 工作小組正式將基於 QUIC 協議的 HTTP(英語:HTTP over QUIC)重新命名為HTTP/3。

所以,我們現在所提到的HTTP/3,其實就是HTTP over QUIC,即基於QUIC協議實現的HTTP。

那麼,想要了解HTTP/3的原理,只需要瞭解QUIC就可以了。

QUIC協議有以下特點:

  • 基於UDP的傳輸層協議:它使用UDP埠號來識別指定機器上的特定伺服器。
  • 可靠性:雖然UDP是不可靠傳輸協議,但是QUIC在UDP的基礎上做了些改造,使得他提供了和TCP類似的可靠性。它提供了資料包重傳、擁塞控制、調整傳輸節奏以及其他一些TCP中存在的特性。
  • 實現了無序、併發位元組流:QUIC的單個數據流可以保證有序交付,但多個數據流之間可能亂序,這意味著單個數據流的傳輸是按序的,但是多個數據流中接收方收到的順序可能與傳送方的傳送順序不同!
  • 快速握手:QUIC提供0-RTT和1-RTT的連線建立
  • 使用TLS 1.3傳輸層安全協議:與更早的TLS版本相比,TLS 1.3有著很多優點,但使用它的最主要原因是其握手所花費的往返次數更低,從而能降低協議的延遲。

那麼,QUIC到底屬於TCP/IP協議族中的那一層呢?我們知道,QUIC是基於UDP實現的,並且是HTTP/3的所依賴的協議,那麼,按照TCP/IP的分層來講,他是屬於傳輸層的,也就是和TCP、UDP屬於同一層。

如果更加細化一點的話,因為QUIC不僅僅承擔了傳輸層協議的職責,還具備了TLS的安全性相關能力,所以,可以通過下圖來理解QUIC在HTTP/3的實現中所處的位置。

接下來我們分別展開分析一下QUIC協議。先來看下他是如何建立連線的。

QUIC的連線建立

我們知道,TCP這種可靠傳輸協議需要進行三次握手,也正是因為三次握手,所以需要額外消耗1.5 RTT,而如果再加上TLS的話,則需要消耗3-4個 RTT連線。

那麼,QUIC是如何建立連線的呢?如何減少RTT的呢?

QUIC提出一種新的連線建立機制,基於這種連線接機制,實現了快速握手功能,一次QUIC連線建立可以實現使用 0-RTT 或者 1-RTT 來建立連線。

QUIC在握手過程中使用Diffie-Hellman演算法來保證資料互動的安全性併合並了它的加密和握手過程來減小連線建立過程中的往返次數。

Diffie–Hellman (以下簡稱DH)金鑰交換是一個特殊的交換金鑰的方法。它是密碼學領域內最早付諸實踐的金鑰交換方法之一。 DH可以讓雙方在完全缺乏對方(私有)資訊的前提條件下通過不安全的通道達成一個共享的金鑰。此金鑰用於對後續資訊交換進行對稱加密。

QUIC 連線的建立整體流程大致為:QUIC在握手過程中使用Diffie-Hellman演算法協商初始金鑰,初始金鑰依賴於伺服器儲存的一組配置引數,該引數會週期性的更新。初始金鑰協商成功後,伺服器會提供一個臨時隨機數,雙方根據這個數再生成會話金鑰。客戶端和伺服器會使用新生的的金鑰進行資料加解密。

以上過程主要分為兩個步驟:初始握手(Initial handshake)、最終(與重複)握手(Final (and repeat) handshake),分別介紹下這兩個過程。

初始握手(Initial handshake)

在連線開始建立時,客戶端會向服務端傳送一個打招呼資訊,(inchoate client hello (CHLO)),因為是初次建立,所以,服務端會返回一個拒絕訊息(REJ),表明握手未建立或者金鑰已過期。

但是,這個拒絕訊息中還會包含更多的資訊(配置引數),主要有:

  • Server Config:一個伺服器配置,包括伺服器端的Diffie-Hellman演算法的長期公鑰(long term Diffie-Hellman public value)
  • Certificate Chain:用來對伺服器進行認證的信任鏈
  • Signature of the Server Config:將Server Config使用信任鏈的葉子證書的public key加密後的簽名
  • Source-Address Token:一個經過身份驗證的加密塊,包含客戶端公開可見的IP地址和伺服器的時間戳。

在客戶端接收到拒絕訊息(REJ)之後,客戶端會進行資料解析,簽名驗證等操作,之後會將必要的配置快取下來。

同時,在接收到REJ之後,客戶端會為這次連線隨機產生一對自己的短期金鑰(ephemeral Diffie-Hellman private value) 和 短期公鑰(ephemeral Diffie-Hellman public value)。

之後,客戶端會將自己剛剛產生的短期公鑰打包一個Complete CHLO的訊息包中,傳送給服務端。這個請求的目的是將自己的短期金鑰傳輸給服務端,方便做前向保密,後面篇幅會詳細介紹。

在傳送了Complete CHLO訊息給到伺服器之後,為了減少RTT,客戶端並不會等到伺服器的響應,而是立刻會進行資料傳輸。

為了保證資料的安全性,客戶端會自己的短期金鑰和伺服器返回的長期公鑰進行運算,得到一個初始金鑰(initial keys)。

有了這個初識金鑰之後,客戶端就可以用這個金鑰,將想要傳輸的資訊進行加密,然後把他們安全的傳輸給服務端了。

另外一面,接收到Complete CHLO請求的伺服器,解析請求之後,就同時擁有了客戶端的短期公鑰和自己儲存的長期金鑰。這樣通過運算,服務端就能得到一份和客戶端一模一樣的初始金鑰(initial keys)。

接下來他接收到客戶端使用初始金鑰加密的資料之後,就可以使用這個初識金鑰進行解密了,並且可以將自己的響應再通過這個初始金鑰進行加密後返回給客戶端。

所以,從開始建立連線一直到資料傳送,只消耗了初始連線連線建立的 1 RTT

最終(與重複)握手

那麼,之後的資料傳輸就可以使用初始金鑰(initial keys)加密了嗎?

其實並不完全是,因為初始金鑰畢竟是基於伺服器的長期公鑰產生的,而在公鑰失效前,幾乎多有的連線使用的都是同一把公鑰,所以,這其實存在著一定的危險性。

所以,為了達到前向保密 (Forward Secrecy) 的安全性,客戶端和服務端需要使用彼此的短期公鑰和自己的短期金鑰來進行運算。

在密碼學中,前向保密(英語:Forward Secrecy,F.........