TLS 1.0 至 1.3 握手流程詳解


概述

TLS 全稱為 Transport Layer Security(傳輸層安全),其前身是 SSL,全稱為 Secure Sockets Layer(安全套接字層),它的作用是為上層的應用協議提供安全的通信,比如眾所周知的 HTTP + TLS = HTTPS。

SSL 2.0 是該協議的第一個公開發布的版本,由於其存在的安全問題很快被升級到了 SSL 3.0,並且在 1999 年,IETF 小組將該協議標准化,因此 TLS 1.0 誕生了。

本文將介紹 TLS 1.0 到 TLS 1.3 的握手流程,你只需要一點點的 SSL\TLS 或 HTTPS 的前置知識與了解過簡單的密碼學上的概念,如對稱加密、非對稱加密、哈希算法等名詞。


從此開始

我們先從一個最簡化的 TLS 握手流程開始:

  1. Client 向 Server 請求建立連接。
  2. Server 將自己的證書發送給 Client。
  3. Client 驗證證書,然后使用證書中的公鑰加密接下來要用來通信的密鑰,將加密結果發送給 Server。
  4. Server 收到后進行響應,且將用該密鑰來對需要發送或接收的上層數據進行加解密。
  5. 自此 TLS 握手完成,接下來開始使用密鑰進行通信。

以下為簡單的流程圖:

在上面中,我們使用的是 RSA 算法來進行密鑰的交換。

RSA 應該是最多人了解的非對稱加密算法,其在這里用於密鑰協商(交換)和證書驗證,這分別用到了它的兩個特性:

  1. 公鑰加密,對應私鑰才能解密
  2. 私鑰加密,對應公鑰可以解密

證書機制用到了第二個機制。

證書實際上就是一段符合指定標准的文本,其大致上記錄了:

  • 證書所屬的域名
  • 域名所有者的公鑰
  • 有效日期
  • 使用的加密算法

CA(證書頒發者)會對這些內容進行使用哈希函數導出一個值(簡單來講就是用 md5 類似的算法計算一下),該值將被 CA 的私鑰進行加密(這樣的操作也叫做"簽名"),同時簽名也將被記錄到證書上。

而"證書驗證"這個行為,當然指的就是使用證書頒發者公布的公鑰,來對被加密的值進行解密,然后對證書使用同樣的哈希函數進行計算,並使用計算出的值與解密出的值進行對比,如果相等則說明沒有被篡改過,可以放心的使用證書上的公鑰進行通信。

而在上文的握手過程中,由於通信密鑰在發送給 Server 時被公鑰所加密了,而用來解密的私鑰自始自終都只被證書申請者持有,所以即使有人截取了所有通信的信息,都不能得到通信的密鑰(當然這是在假定密鑰永遠不會泄露的情況下)。

這樣,我們就建立了一個安全的"信道"。


嗯...到這里還算是比較常規,接下來就是實際上的 TLS 協議了,不過規范定義中的流程和上面的版本有一點點變化

TLS 1.0

握手流程

  1. Client 向 Server 發送一個隨機數、客戶端可使用的加密組件、壓縮算法和可能的 Session ID。

  2. Server 接收到消息后返回證書、Session ID、准備使用的加密組件和可能使用的壓縮方法,同時還會生成一個隨機數並返回。

  3. Client 收到后進行證書的驗證,然后根據密鑰協商協議向 Server 發送規定的密鑰交換信息。

  4. Server 和 Client 一起使用 \(pre\ master\ secret\) 與之前的隨機數,通過協商好的加密算法生成 \(master\ secret\)

    然后,Server 和 Client 將開始使用 \(master\ secret\) 作為通信密鑰進行通信。

  5. 緊接着,Client 和 Server 互相向對方發送本次握手中所有報文生成的 摘要(Hash 算法的計算結果),同時也是生成了 \(master\ secret\) 后發送的第一條加密信息。

  6. 當 Client 收到 Server 發來的摘要后,就可以正式開始發送應用數據。

如果你是第一次了解到以上的握手流程,那在看完后多半會發出一個“?”,所以我們接下來將詳細的介紹各個步驟。


RSA 加密組件

首先需要介紹的是加密組件,TLS 1.0 中的非對稱加密的算法包含 RSA 和 DH/DHE,在密鑰協商階段我們可以任意選用一種,我們先來了解使用我們最熟悉的 RSA 作為密鑰協商算法的情況。

第一步

Client 向 Server 發送一個隨機數、自己支持的加密組件、壓縮算法和可能的 Session ID。

這一步很好理解,Client 需要告知 Server 自己支持的加密算法,讓 Server 進行選擇,接下來才能進行通信。同時我們可以告知 Server 我們希望使用的壓縮算法,如果 Server 也支持,那在之后傳輸上層的應用數據時將會使用該壓縮算法進行解壓縮。

需要提醒的是,壓縮功能 TLS 不建議啟用,甚至在 TLS 1.3 時相關功能被直接禁用。

這一方面是由於壓縮功能並不是 TLS 協議的本職,同時也是因為壓縮功能帶來了安全上的問題。

"可能的 Session ID"則是在 Client 在重連 Server 時減少握手成本的一個機制,即在重連時並不需要重新走一次握手流程,只需要帶着 Session ID,就能重新連接,具體的使用我們會在后面再詳細介紹。


第二步

在 Server 收到 Client 的連接建立的請求后,將返回自己的證書;並且如果啟用了 Session ID 的話則會分配一個 Session ID 並返回,這樣的話當下次建立連接時,Client 將會帶上 Session ID,就不需要走完整的握手過程。

當然 Session ID 也可以為空,這樣的話代表 Server 不希望緩存會話。

並且還會根據自身的情況返回接下來將使用的加密套件名稱和壓縮算法的名稱,同時還會生成一個隨機數並返回。


第三步

Client 在收到證書后,將進行"驗證證書",證書合法才會繼續下一步。

然后,Client 將生成 \(pre\ master\ secret\) ,具體是啥我們看一下它的結構就知道了:

struct {
  ProtocolVersion client_version;
  opaque random[46];
} PreMasterSecret;

第一個字段是客戶端版本,用來檢測 降級攻擊;第二個字段則是一個隨機數。

降級攻擊(downgrade attack)

攻擊者故意欺騙 Server 和 Client 使用舊的、不安全的協議,即使它們都支持最新的協議。常用於中間人攻擊中,由於握手階段是明文的,所以攻擊者可以任意的篡改,故需要防止這種攻擊。

比如美國在早期時候禁止超過指定長度的算法出口,所以 TLS 帶有了一些不安全的算法。攻擊者可以嘗試修改明文報文來欺騙雙方使用不安全的 TLS 版本(比如現在正在介紹的 TLS 1.0),以此可以更簡單的破解中間使用的不安全的算法加密的應用數據。

但由於 TLS 在這里加上了 client_version 字段,並且本條數據將會被加密發送,所以實際上攻擊者無法篡改。


\(pre\ master\ secret\) 是用來生成 \(master\ secret\) 的重要參數,所以接下來將使用證書上的公鑰對 \(pre\ master\ secret\) 加密,並發送給 Server


第四步

接下來,Client 和 Server 雙方都將使用(客戶端隨機數)+(服務器端隨機數)+( \(pre\ master\ secret\) )來生成 \(master\ secret\)

\(master\ secret\) 將被作為通信的密鑰。不過,我們為什么要這么做呢?


為什么不直接使用公鑰加密 \(master\ secret\) 的方式來做密鑰交換?

仔細一想,這不是挺好的嗎?雖然握手流程一次也少不了,但至少少了好幾個"莫名其妙"的參數?

不過實際上,如果不考慮其他密鑰協商算法的話,這確實是一個不錯的選擇。但是別忘了,在我們思考的上面的密鑰協商的流程中,使用的加密套件是 RSA,而對於 TLS 協議來講,還具有其他密鑰協商算法,例如在 TLS 1.0 中就具有 RSA 和 DH/DHE 兩種,所以這其實是一個協議設計上的問題。

TLS 協議加入了 \(pre\ master\ secret\) 來生成 \(master\ secret\) 的流程,最主要還是為了使得協議能更好的模塊化(應該算是一種"模板方法"的思想?)


為什么要使用到這么多隨機數?

對於 \(master\ secret\) ,TLS 1.0 中的生成公式為:

\[\begin{aligned} master\_secret=PRF(&pre\_master\_secret,\\ &\text{"master secret"},\\ &ClientHello.random + ServerHello.random) [0..47]; \end{aligned} \]

其中 \(PRF\) 我們可以簡單理解為一個 Hash 函數。然后我們首先來考慮 \(pre\ master\ secret\) ,這是可以去掉的隨機數嗎?

應該不是,對於 RSA ,它是屬於由客戶端生成的隨機數;但對於 DH/DHE 來講,其是通過密鑰協商得到的密鑰(具體會在后面介紹),所以不屬於隨機數,不能夠被去掉。

那服務器端隨機數呢?對於服務器端隨機數,它最大的用途就是用來防止 重放攻擊

重放攻擊(replay attack)

該攻擊指的是在通信的時候,攻擊者截取通信的一部分,然后在日后的某時重新向服務器發送該段被階段的報文。

最重要的是,即使通信的信道是保密的(通信信息被加密),攻擊者也可以使用該方法來進行搗蛋,即使它並不能理解通信的信息。

比如在下單付款時,攻擊者截取了你的從下單到付款的 HTTPS 的流量,然后進行重放,使得你多下單付款了好幾個訂單。(當然這個例子不夠恰當,因為對於付款這種流程幾乎都是要多因素身份驗證(MF)的,比如使用支付寶付款時手機至少得接收個驗證碼吧,更何況支付的時候肯定是有對應的冪等性方案的)

所以,由於我們在通信密鑰的生成中加入了來自 Server 的隨機數,所以即使攻擊者收集了 Client 發向 Server 的報文,但由於每次的服務端隨機數都不一樣,所以兩次握手協商出的 \(master\ secret\) 必然是不一樣的,這樣的話攻擊者最后會在發送第一次消息的階段就會露餡。

最后再看看客戶端隨機數,同樣的這里的隨機數也可以用來防止攻擊者收集 Server 發向 Client 的數據進行重放(不過這樣做大多數情況下意義不大)。並且客戶端隨機數還可以為最后的 \(master\ secret\) 的生成貢獻"熵"(相當於鹽(salt)一樣)。


第五步

在得到了 \(master\ secrrt\) 后,雙方會計算出本次握手中的包的摘要,並直接發送給對方(已被加密),當一方收到來自另一方的發來摘要后,將正式進行上層應用數據的通信。


其流程圖大概是這樣:



DH 加密組件

在介紹 DH 加密組件的握手流程之前,我們先了解下 DH 算法具體是干啥的

DH 算法介紹

DH 算法也屬於非對稱加密算法,其在此用於密鑰協商(和 RSA 相比,RSA 更像是一個密鑰交換算法),其目的是在不安全的信道下協商出一個密鑰以加密未來的通信信息使得建立一個安全的信道(不可監聽、不可篡改)。

對於 DH 算法的具體內容,由於我們要討論的不是算法的實現,所以為了避免陷入過多的數學細節,我們將其具體算法抽象為一個函數(你也可以點擊這里了解 DH 算法的具體實現):

\[f(p,g,n)=N \]

該函數中,\(p\)\(g\) 在一次密鑰的協商的過程中是不變的,我們只需要關心 \(n\)

該函數的 \(n\) 我們稱為私鑰,計算出的 \(N\) 稱為公鑰,且根據 \(N\) 難以推出 \(n\) (就像是哈希函數一樣);而對於使用同一個 \(p\)\(g\) 計算出的多個密鑰對(\(a\), \(A\))、(\(b\), \(B\)),具有一個特殊的性質:

\[p(a,B)=K=p(b,A) \]

其中 \(K\) 為協商出來的密鑰。但這又有什么用呢?我們再來重新回顧下握手流程,這次使用 DH 作為密鑰協商算法。


握手流程

第一步與使用的密碼套件無關,因此客戶端的行為是相同的。

我們具體來看第二步,這一步中主要的不同在於返回的證書,這里的證書分兩種,一種是在 RSA 中見到的,記錄的是 RSA 的公鑰,而另一種記錄的是 DH 算法中的 \(p\)\(g\)\(B\)\(B\) 是由 Server 的私鑰 \(b\) 通過 \(f(p,g,n)\) 函數計算得到的公鑰)。

在 DH 密碼組件中,DHE 算法會使用前者,而 DH 算法會使用后者,這兩者之間的區別在於是否具有"前向安全性"(后文會詳細介紹)。

在第二步中,如果使用的是 DH 算法,將會直接把證書發送過去;而如果是 DHE,則除了發送證書外,還會使用證書上的 RSA 密鑰對來對新生成的 \(p\)\(g\)\(B\) 進行簽名,並和這幾個參數一起向 Client 發送。

在 TLS 1.2 時,除了對 \(p\)\(g\)\(B\) 簽名,還會對客戶端隨機數和服務器端隨機數進行簽名。


在第三步中,當 Client 驗證完證書后,接着會向 Server 發送進行密鑰協商所需要的信息。對於 RSA 來講,這一步要向 Server 發送 \(pre\ master\ secret\) ,而對於 DH 算法,則需要發送的是 \(A\)\(A\) 是由客戶端生成的私鑰 \(a\) 通過 \(p\)\(g\) 計算所得出的公鑰。

其中客戶端得到的 \(p\)\(g\) 來自於證書或為直接收到的參數。

接下來 \(A\) 被發送給 Server 后,Client 接着使用 \(p(a,B)\) 計算出 \(K\) 作為 \(pre\ master\ secret\) ,參與最終的 \(master\ secert\) 的計算。

而對於 Server,也可以使用 \(p(b,A)\) 生成 \(K\) 作為 \(pre\ master\ secret\) ,由於我們在上面介紹的特殊的性質,這兩個 \(K\) 一定是相等的。

之后的過程則是和使用其他密鑰組件相同。

以下為簡單的流程圖:



我們再來思考一下,這種方法安全嗎?

首先考慮我們作為中間人能獲得到哪些信息,在整個握手的流程中,我們可以截獲 \(A\)\(B\)\(p\)\(g\) 、證書和兩個隨機數,但是參與 \(K\) 的計算的 \(a\)\(b\) 都沒有被泄露,且不可能根據 \(A\) 計算出 \(a\) 或根據 \(B\) 計算出 \(b\) ,所以最后用來通信的 \(master\ secert\) 也不可能被知道。

那中間人攻擊呢?如果我們能將 \(B\)\(b\) 替換為作為攻擊者的我們的 \(C\)\(c\) ,那么也是可以得到 \(master\ secret\) 的。但是, \(B\) 要么是直接被證書所保護(DH 算法),要么是被證書上的 RSA 密鑰對簽名(DHE算法),所以篡改也變得不可能了。

當然以上只是簡單的推理,實際上在密碼學中會進行嚴格的證明


缺點

到這里,你發現了 TLS 1.0 的幾個缺點了嗎?

最顯著的,就是"慢",每建立一次 TLS 連接都至少需要 \(2\cdot RTT\) ,如果 http 使用 TLS 進行保護,那么如果每次加載一個資源都建立一條連接,那不是每個資源都會比普通的 http 慢上 \(2\cdot RTT\)

別擔心,這里只是舉個例子,http 1.1 時就默認開啟了持久連接,http 2.0 時啟用了二進制分幀,一般不會為單獨一個資源建立一條連接的,因此其實只會在初次連接時慢。

實際上 TLS 也是有對應的解決方案的,使用的當然就是萬能的緩存,還記得在一開始提到的 Session ID 嗎?


Session ID

Session ID 為第一次握手時候發送的 Session ID 字段的值,服務器端會維護對於該 Session ID 對應的通信密鑰,當 Client 再次連接的時候,只需要在頭部中加上 Session ID,即可通知 Server 重新使用上次協商過的通信密鑰進行通信,這樣就不需要每一次都重新協商來浪費資源,同時由於不需要完整的握手過程,所以握手時間也減少了 \(1\cdot RTT\)

但這樣的缺點也是十分明顯的,首先是單機問題,一個池子只能在單台機器上使用。並且當連接數較大時效果可能會降低,這是因為當我們的緩存池過小會起不到作用,因為連接數大時之前的 Session ID 會很快的失效;而緩存池大時,Server 則需要更多的內存資源去維護 Session ID 到通信密鑰的映射。


Session Ticket

不過除了 Session ID 外,還有一個叫做 Session Ticket 的東西。和 Session ID 相同,Session Ticket 也是一種無需重新協商密鑰的方案。

當 Client 明確支持 Session Ticket 時,Server 會在握手結束后,向 Client 發送被加密的恢復連接所需要的數據,同時 Server 無需保存任何信息,且由於此時握手已經完成,所以信道是安全的。

在下次建立 TLS 連接時,Client 只需要在第一次握手時發送上次收到的"被加密的恢復連接所需的數據",Server 則可以通過只有自己知道的密鑰解密該握手數據,並重新使用上次的通信密鑰。

一個更簡單的理解:Session ID 相當於 HTTP 中的 Session,Session Ticket 相當於 HTTP 中的 Cookies

以下為使用 Session ID 或 Session Ticket 時的握手流程:

需要注意的是,Client 在第一次握手時仍然攜帶了較為完整的握手報文,這是為了當 Server 發現 Session ID 或 Session Ticket 過期的時候能夠方便的退化為普通的握手流程。


前向安全性

前向安全性(Forward Secrecy)指的是過去的通信的安全性不會受到未來的密鑰泄露事件的影響。RSA 和 DH 的密鑰協商方式在 TLS 1.3 中被刪除就是因為不具備前向安全性。

拿 RSA 舉例,如果我們一直使用 RSA 的密鑰協商方式進行通信,雖然攻擊人不能立刻的解密出通信密鑰,但是可以持續的收集這些被加密的通信內容。直到某一天你的服務器終於被攻破,證書對應的私鑰被攻擊人拿到,那么他將可以使用私鑰解密你在之前的所有連接建立時被加密的 \(pre\ master\ secret\) ,並生成 \(master\ secret\) ,然后可以獲得使用該密鑰加密的所有會話數據。

除了服務器被攻破外,社會工程學和"FBI open the door"時的情況是一樣的,它們都屬於密鑰泄露事件

在這種情況下,由於被攻破的時間點前的被加密的通信數據會受到影響。所以不具備前向安全性。

同樣的,靜態的 DH 算法由於公鑰是固定在證書上的,所以也不具備前向安全性。

那 DHE 呢?由於它的私鑰 \(n\) 每次都會重新生成,其證書只用於保證公鑰 \(N\) 沒有被篡改,在一次通信完成后私鑰就會被丟棄,所以攻擊者無法取得。中間人即使獲得了證書的私鑰,也只能使用中間人攻擊獲得在那之后的通信數據,而在那之前的通信數據則無法取得,所以說 DHE 具備前向安全性。



TLS 1.2

False Start

TLS 的握手流程在 1.1 和 1.2 的標准發布時都沒有發生較大的變化,不過在 RFC7918 中對於 TLS 握手增加了一個叫做 False Start (搶跑)的小優化。

該優化在於 Client 無需等待收到來自 Server 的 finish 響應就可以開始發送被加密的業務數據,就是說減少了一個 \(RTT\)

不過為了安全(由於是在 finish 消息前就開始發送業務數據,所以報文有被篡改的風險),這個小優化需要使用的密鑰協商算法具有前向安全性(例如 DHE 或 ECDHE)。

當啟用 False Start 時的 DHE/ECDHE 算法的握手流程如下:

ECDH 算法使用了與 DH 算法不同的數學難題,不過我們仍然可以使用之前定義過的模型來理解 ECDH(E) 的密鑰協商過程。



TLS 1.3

介紹

TLS 的握手流程從 1.0 發布(1999)到 1.3 正式發布(2018),終於迎來了較大的變動(在這之前的 1.1 和 1.2 大多只是對於加密算法的更新和安全漏洞的修復)。


首先,之前我們介紹過的 Session Ticket 機制和 Session ID 機制都被廢棄了,其使用了新的機制來代替,並且改變了密鑰派生的機制,廢棄了一大堆已經不安全的算法,同時直接用 RSA 來交換密鑰的方式和 DH 密碼套件被廢棄,現在使用的是 DHE/ECDHE,並且握手信息也被部分的進行了加密。

現在的連接建立的方式大體上使用的是 DHE/ECDHE 或 PSK。


其中對於握手流程來講,最大的變化是之前介紹的 \(2\cdot RTT\) 變成了 \(1\cdot RTT\) 甚至 \(0\cdot RTT\) 。而之所以能做到 \(1\cdot RTT\) ,是因為 TLS 1.3 中將 Client 進行密鑰協商的時機提前了。

具體怎么做的呢?我們先來簡單回憶一下 Client 的密鑰在 1.3 前的 DH(E) 算法下是怎么進行協商的:

  1. Client -> Server:請求建立連接
  2. Server -> Client:生成 \(b\) 並計算出 \(B\) 發向對方
  3. Client -> Server:生成 \(a\) 並計算出 \(A\) 發向對方
  4. 雙方根據 \(a\cdot B\)\(b\cdot A\) 計算出密鑰 \(K\)

在這里我們可以看出 Client 向 Server 發送密鑰是在收到 Server 的響應后的,那為什么要這么晚才發送呢?這是因為函數 \(f(p,g,n)=N\) 的計算需要 \(p\)\(g\) ,而他們又在 Server 的證書里或被證書所簽名,所以需要等到收到 Server 證書后才能計算。也就是說,只要我們能夠提前得知 \(p\)\(g\) ,就能在第一個握手時就發送 \(A\)

TLS 1.3 就是采用的這樣的做法,它直接在協議內部固定了 \(p\)\(g\) 的值,將其限定在幾個值內,因此就能夠提前的計算出 \(A\)

因此 Client 將可以提前生成好 \(a\) 並通過多個固定的 \(p\)\(g\) 直接計算出 \(A\) 並發送。


握手流程

具體我們直接來看 TLS 1.3 的握手流程:

  1. Client 生成 \(key\_share\) 向 Server 發送

    \(key\_share\) 的結構為一個列表,其中具有多個 <密鑰組名,通過 \(a_n\) 和對應組使用的 \(p\)\(g\) 生成的 \(A_n\) > 的數據項。

    為了安全起見,每一個數據項中的 \(a\) (Client 私鑰) 都是不一樣的

    以上包含的多個組中並不只有 DHE 類型的組,還有 ECDHE 類型,其值中的加密組件所需參數不同

  2. Server 從多個密鑰組中選擇自己能夠接受的密鑰組;如果沒有,則會直接響應自己能夠接受的密鑰組的組名,讓 Client 重新生成對應的 \(A\) 並發送(此時 \(1\cdot RTT\) 會退化為 \(2\cdot RTT\) )。

    在選定密鑰組后,Server 發送決定使用的密鑰組的名稱,同時使用該組的 \(p\)\(g\) 和自己的 \(b\) 計算出 \(B\) ,緊接着根據密鑰交換原理通過收到的 \(A\) 計算出 \(K\)

    在將"服務器端隨機數"和 \(B\) 發送出去后,接下來,將通過 \(K\) 導出以下三個密鑰:

    • \(master\ secret\) :主密鑰
    • 客戶端握手密鑰:用來加密 Client -> Server 的握手信息
    • 服務器端握手密鑰:用來加密 Server -> Client 的握手信息

    導出密鑰后,接下的所有要發送的信息都將被對應的密鑰進行加密。

    需要注意的是,盡管以上描述的結果是正確的的,但是"導出"的過程被省略了,實際上對於每個被導出的密鑰,所參與的參數與過程都存在不同,且該過程中會使用到 [客戶端隨機數] 和 [服務器端隨機數],這兩參數的交換過程也被省略。如果你對於"導出"的過程感興趣,建議閱讀 RFC8446

    導出的操作可理解為使用單向散列函數,以上導出的每個密鑰中,參與導出的參數並不完全相同,所以導出的密鑰的值也並不同的。

  3. Client 收到來自 Server 的服務器端隨機數和公鑰 \(B\) 后,也進行以上的計算過程,並根據得出的"服務器端握手密鑰"解密接下來收到的握手信息。

    在解密並驗證完被加密的證書后,就能確保服務器的身份。同時仍會驗證發過來的握手階段的報文的摘要,確認握手過程未被篡改。

    在保證通信安全后,接下來會和 TLS 1.3 前一樣發送握手階段報文的摘要(當然,這會被"客戶端握手密鑰"加密)。

  4. 在兩邊做完自己的工作后,雙方將從 \(master\ secret\) 中導出以下四個密鑰:

    • 客戶端通信密鑰:用來加密 Client -> Server 的通信信息

      (上文的兩個用於握手報文加密的密鑰將會被丟棄)

    • 服務器端通信密鑰:用來加密 Server -> Client 的通信信息

    • 恢復密鑰:用來參與 PSK 的計算

    • 導出密鑰:用來參與默認的密鑰導出計算

  5. 接下來,雙方將開始使用各自的通信密鑰發送應用數據

以下為簡易的握手流程圖:



介紹完 TLS 1.3 后,我猜你的第一個問題是:

為什么使用了這么多不同的密鑰?

在 TLS 1.3 中,其密鑰的導出函數叫做 HKDF,為一個基於 HMAC 的密鑰導出函數(KDF),它執行的主要方法是 "extract-then-expand",也就是說,先從輸入密鑰與參數中提取一個固定長度的密鑰,然后拓展為多個額外的密鑰。重點在於,額外導出的密鑰在密碼學上是安全的,並且即使其中一個密鑰被泄露,也不會導致其他由相同的密鑰材料導出的密鑰存在風險。

簡單來講就是,"不要把雞蛋放在同一個籃子里"。



Pre-share key(PSK)

PSK 是 TLS 1.3 實現 \(0\cdot RTT\) 的重要功能,它的全稱是 \(pre\ share\ key\) ,從名稱你應該已經知道了,它是用於會話恢復的一個機制,同時 Session ID 和 Session Ticket 機制在 TLS 1.3 中被廢棄也是因為其作用被該功能所替代了。


Zero-RTT

我們首先來關心 PSK 從何而來

在握手流程結束后,Server 可以發送任意數量的 ticket 給 Client,由於此時的信道被各自的通信密鑰所保護,所以直接的發送是安全的。和 ticket 一起發送的還有該 ticket 的過期時間。

我們都知道 PSK 可以用來恢復通信,所以泄露可能會導致一定程度的危險,於是為了最大程度減少風險,加上過期時間是必要的。

然后,雙方根據 ticket 的序號與由 \(master\ sectet\) 導出的恢復密鑰導出得到 PSK。

Client 在導出 PSK 后會將其以及相關的信息后存到該網站下的本地緩存中,它將會在下次進行 TLS 握手的時候使用。

當第二次想和網站進行 TLS 握手時,在發送的時候除了發送 \(key\ share\) ,還會發送 PSK 對應的 ticket 和它的簽名。同時還會通過 PSK 導出以下三個密鑰:

  • binder 密鑰:用來對發送的 ticket 進行簽名(防止篡改)
  • 早期數據加密密鑰:用來對啟用 \(0\cdot RTT\) 時想要發送的應用數據進行加密的密鑰
  • 早期導出密鑰:在計算出"導出密鑰"前代替它的作用

而此時如果需要 \(0\cdot RTT\) ,則可以使用"早期數據加密密鑰"來對需要發送的數據進行加密。而 Server 則可以根據 ticket 來驗證並得到 PSK 以解密應用數據,同時由於身份已經被 PSK 機制所驗證,因此不需要再次發送證書,此時只需要走簡單的密鑰協商的流程即可。

以下為簡單的流程圖:


Ticket

我們再回過頭來看在會話恢復流程中以明文傳輸的 ticket 到底是什么。

我們說過 PSK 機制替代了 Session ID 和 Session Ticket ,這也就意味着 PSK 兩種角色都可以扮演。也就是說,ticket 的值可以像是 Session ID 一樣為一個在數據庫中進行查找的鍵,也可以是像 Session Ticket 為一個自加密、自驗證的值。

所以 Server 在驗證的時候會根據使用的 ticket 不同來具體的驗證行為。比如說可以像 Session ID 一樣,檢查本地緩存是否存在對應的鍵,存在則取出,而它的值可能是上一次握手協商得到的 PSK 值,此時可以直接取出並也導出對應的三個密鑰,並對數據進行驗證和解密。又或者和 Session Ticket 一樣,使用本地的密鑰對該 ticket 進行解密,而解密出的可能是 PSK 的值,此時同樣會導出密鑰以進行驗證與解密。


安全性

如果你的足夠敏銳的話,此時應該已經發現了 PSK 的一個缺點:在使用 PSK 發送早期數據時,此時的加密早期數據的密鑰派生於 PSK,而 PSK 是不具備前向安全性的!也就是說如果某時的攻擊者獲得了 PSK ,那么使用該 PSK 導出的密鑰加密的早期數據將會被解密。(當然,這不包括在建立連接后的應用數據,因為它們使用的是具有前向安全的 DHE/ECDHE 算法協商出的密鑰)

一種簡單的解決方式就是盡量的使用較短有效期的 PSK,TLS 1.3 在設計的時候就想到了這點,所以添加了有效期字段,如果在乎前向安全的話,一般設置一個較短的有效期即可解決。

在其他的問題上,攻擊者還可以截取這部分的早期數據進行重放攻擊,所以對於早期應用數據,冪等性也是必要的。



參考資料

RFC 2246

RFC 4346

RFC 5246

RFC 8446

RFC 9001

《Web性能權威指南》


Source:https://www.cnblogs.com/enoc/p/tls-handshake.html



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM