HTTPS之TLS1.2連接過程(三)

NO IMAGE

熟悉了對稱加密與非對稱加密、數字簽名與證書等密碼學知識。就可以正式開始研究 HTTPS 和 TLS 協議了。

HTTPS 建立連接

當你在瀏覽器地址欄裡鍵入“https”開頭的 URI,再按下回車,會發生什麼呢?

你應該知道,瀏覽器首先要從 URI 裡提取出協議名和域名。因為協議名是“https”,所以瀏覽器就知道了端口號是默認的 443,它再用 DNS 解析域名,得到目標的 IP 地址,然後就可以使用三次握手與網站建立 TCP 連接了。

在 HTTP 協議裡,建立連接後,瀏覽器會立即發送請求報文。但現在是 HTTPS 協議,它需要再用另外一個“握手”過程,在 TCP 上建立安全連接,之後才是收發 HTTP 報文。

這個“握手”過程與 TCP 有些類似,是 HTTPS 和 TLS 協議裡最重要、最核心的部分,懂了它,你就可以自豪地說自己“掌握了 HTTPS”。

TLS 協議的組成

先簡單介紹一下 TLS 協議的組成。

TLS 包含幾個子協議,你也可以理解為它是由幾個不同職責的模塊組成,比較常用的有記錄協議、警報協議、握手協議、變更密碼規範協議等。

記錄協議(Record Protocol)規定了 TLS 收發數據的基本單位:記錄(record)。它有點像是 TCP 裡的 segment,所有的其他子協議都需要通過記錄協議發出。但多個記錄數據可以在一個 TCP 包裡一次性發出,也並不需要像 TCP 那樣返回 ACK。

警報協議(Alert Protocol)的職責是向對方發出警報信息,有點像是 HTTP 協議裡的狀態碼。比如,protocol_version 就是不支持舊版本,bad_certificate 就是證書有問題,收到警報後另一方可以選擇繼續,也可以立即終止連接。

握手協議(Handshake Protocol)是 TLS 裡最複雜的子協議,要比 TCP 的 SYN/ACK 複雜的多,瀏覽器和服務器會在握手過程中協商 TLS 版本號、隨機數、密碼套件等信息,然後交換證書和密鑰參數,最終雙方協商得到會話密鑰,用於後續的混合加密系統。

變更密碼規範協議(Change Cipher Spec Protocol),它非常簡單,就是一個“通知”,告訴對方,後續的數據都將使用加密保護。那麼反過來,在它之前,數據都是明文的。

下面的這張圖簡要地描述了 TLS 的握手過程,其中每一個“框”都是一個記錄,多個記錄組合成一個 TCP 包發送。所以,最多經過兩次消息往返(4 個消息)就可以完成握手,然後就可以在安全的通信環境裡發送 HTTP 報文,實現 HTTPS 協議。

HTTPS之TLS1.2連接過程(三)

ECDHE 握手過程

剛才的是握手過程的簡要圖,這裡有一個詳細圖

HTTPS之TLS1.2連接過程(三)

在 TCP 建立連接之後,瀏覽器會首先發一個“Client Hello”消息,也就是跟服務器“打招呼”。裡面有客戶端的版本號、支持的密碼套件,還有一個隨機數(Client Random),用於後續生成會話密鑰。

Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Random: 1cbf803321fd2623408dfe…
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

服務器收到“Client Hello”後,會返回一個“Server Hello”消息。把版本號對一下,也給出一個隨機數(Server Random),然後從客戶端的列表裡選一個作為本次通信使用的密碼套件,在這裡它選擇了“TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384”。

Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

這個的意思就是:“版本號對上了,可以加密,你的密碼套件挺多,我選一個最合適的吧,用橢圓曲線加 RSA、AES、SHA384。我也給你一個隨機數,你也得留著。”

然後,服務器為了證明自己的身份,就把證書也發給了客戶端(Server Certificate)。

接下來是一個關鍵的操作,因為服務器選擇了 ECDHE 算法,所以它會在證書後發送“Server Key Exchange”消息,裡面是橢圓曲線的公鑰(Server Params),用來實現密鑰交換算法,再加上自己的私鑰簽名認證。

Handshake Protocol: Server Key Exchange
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: x25519 (0x001d)
Pubkey: 3b39deaf00217894e...
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature: 37141adac38ea4...

這相當於說:“剛才我選的密碼套件有點複雜,所以再給你個算法的參數,和剛才的隨機數一樣有用,別丟了。為了防止別人冒充,我又蓋了個章。”

之後是“Server Hello Done”消息,服務器說:“我的信息就是這些,打招呼完畢。”

這樣第一個消息往返就結束了(兩個 TCP 包),結果是客戶端和服務器通過明文共享了三個信息:Client RandomServer RandomServer Params

客戶端這時也拿到了服務器的證書,那這個證書是不是真實有效的呢?

走證書鏈逐級驗證,確認證書的真實性,再用證書公鑰驗證簽名,就確認了服務器的身份:“剛才跟我打招呼的不是騙子,可以接著往下走。”

然後,客戶端按照密碼套件的要求,也生成一個橢圓曲線的公鑰(Client Params),用“Client Key Exchange”消息發給服務器。

Handshake Protocol: Client Key Exchange
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…

現在客戶端和服務器手裡都拿到了密鑰交換算法的兩個參數(Client Params、Server Params),就用 ECDHE 算法一陣算,算出了一個新的東西,叫“Pre-Master”,其實也是一個隨機數。

現在客戶端和服務器手裡有了三個隨機數:Client RandomServer RandomPre-Master。用這三個作為原始材料,就可以生成用於加密會話的主密鑰,叫“Master Secret”。而黑客因為拿不到“Pre-Master”,所以也就得不到主密鑰。

為什麼非得這麼麻煩,非要三個隨機數呢?

這就必須說 TLS 的設計者考慮得非常周到了,他們不信任客戶端或服務器偽隨機數的可靠性,為了保證真正的“完全隨機”“不可預測”,把三個不可靠的隨機數混合起來,那麼“隨機”的程度就非常高了,足夠讓黑客難以猜測。

有了主密鑰和派生的會話密鑰,握手就快結束了。客戶端發一個“Change Cipher Spec”,然後再發一個“Finished”消息,把之前所有發送的數據做個摘要,再加密一下,讓服務器做個驗證。

意思就是告訴服務器:“後面都改用對稱算法加密通信了啊,用的就是打招呼時說的 AES,加密對不對還得你測一下。”

服務器也是同樣的操作,發“Change Cipher Spec”和“Finished”消息,雙方都驗證加密解密 OK,握手正式結束,後面就收發被加密的 HTTP 請求和響應了。

雙向認證

上面說的是“單向認證”握手過程,只認證了服務器的身份,而沒有認證客戶端的身份。這是因為通常單向認證通過後已經建立了安全通信,用賬號、密碼等簡單的手段就能夠確認用戶的真實身份。

但為了防止賬號、密碼被盜,有的時候(比如網上銀行)還會使用 U 盾給用戶頒發客戶端證書,實現“雙向認證”,這樣會更加安全。

雙向認證的流程也沒有太多變化,只是在“Server Hello Done”之後,“Client Key Exchange”之前,客戶端要發送“Client Certificate”消息,服務器收到後也把證書鏈走一遍,驗證客戶端的身份。

小結

內容比較多、比較難,不過記住下面四點就可以

  1. HTTPS 協議會先與服務器執行 TCP 握手,然後執行 TLS 握手,才能建立安全連接;
  2. 握手的目標是安全地交換對稱密鑰,需要三個隨機數,第三個隨機數“Pre-Master”必須加密傳輸,絕對不能讓黑客破解;
  3. “Hello”消息交換隨機數,“Key Exchange”消息交換“Pre-Master”;
  4. “Change Cipher Spec”之前傳輸的都是明文,之後都是對稱密鑰加密的密文。

相關文章

手寫call、apply、bind

ES6模塊和CommonJS模塊

同源策略

HTTPS之TLS1.3特性解析(四)