真正「搞」懂HTTPS協定17之TLS握手

2023-02-15 21:05:58

  經過前兩章的學習,我們知道了通訊安全的定義以及TLS對其的實現~有了這些知識作為基礎,我們現在可以正式的開始研究HTTPS和TLS協定了。嗯……現在才真正開始。

  我記得之前大概聊過,當你在瀏覽器的位址列輸入一個URL地址會發生什麼,大致是瀏覽器從URI中獲取協定名和域名,獲取預設埠號,再用DNS解析出IP地址,然後就可以三次握手與網站建立TCP連線了,然後就會立即進行報文的傳遞。

  但是在HTTPS中,三次握手後,還不能立即傳送報文,它還需要再用另外一個握手過程,在TCP上建立安全連線,之後才是收發HTTP報文。

  這個握手過程與TCP類似,是HTTPS和TLS協定裡最重要、最核心的部分,搞懂了TLS握手,你就掌握了HTTPS。

一、TLS協定的組成

  在講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),它非常簡單,就是一個「通知」,告訴對方,後續的資料都將使用加密保護。那麼反過來,在它之前,資料都是明文的。

  其中握手協定是最最重要的,也是本章的重點。我們先來看張簡要圖:

   我們來分析下上圖,我們可以粗略的看到三種顏色,嗯……沒錯,我們還可以看到兩次資料的往返。嗯……也沒錯。

  首先,圖中的每一個圓角框,就是一個record,就是一個記錄。多個記錄可以組合成一個TCP包傳送。所以最多兩次往返,4個訊息,就可以完成握手,然後底下的紫色部分,就是加密後的資訊了。

  其次,在TLS握手完成之前,也就是出現ChangeCipherSpec之前,所有的訊息都是明文傳輸的。欸?明文傳輸,那不會洩露交換的資料麼?這個問題我就不回答了,回憶一下我們前兩章講的東西。

  最後,我們簡要的看詞說話一下,首先使用者端會發起一個訊息,攜帶了TLS的版本號,使用者端亂數,還有一個Cipher Suites,Cipher Suites是啥呢?就是密碼套件。還記得我們之前說過,使用者端會把它支援的密碼套件傳送給伺服器,讓伺服器來選擇一個。然後呢,伺服器會把一大堆東西傳給使用者端,其中包括TLS版本、伺服器的亂數、和Cipher Suite、然後還有其它的訊息,比如伺服器證書Certificate,比如PubKey,就是伺服器的公鑰,再有就是傳一個Server完事的資料。下一波呢,使用者端會傳一個PubKey給伺服器,還有ChangeCipherSpec以及結束標記傳送給伺服器。伺服器也返回結束。接下來就開始加密傳輸了。

  當然,這是簡單的過程,大家先消化一下,接下來我們再看看詳細的流程:

  嗯……這張圖確實有點大。不用怕啦,我們一點一點來分析,其實這張圖就是我們本章要講的重點了,都在這張圖裡了。

  第一部分,很好理解,我相信大家都很熟悉了,就是TCP的三次握手嘛~不說了。

  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)

  然後,伺服器為了證明自己的身份,就把證書也發給了使用者端(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 Random、Server Random、Server 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 Random、Server Random 和 Pre-Master。用這三個作為原始材料,就可以生成用於加密對談的主金鑰,叫「Master Secret」。而駭客因為拿不到「Pre-Master」,所以也就得不到主金鑰。那,為啥要三個亂數來生成Master Secret呢?其實就是為了提高破解的複雜度。

  那~~再多說兩句,Master-Secret怎麼計算出來的呢?

master_secret = PRF(pre_master_secret, "master secret",
                    ClientHello.random + ServerHello.random)

  這是RFC中的計算公式。

  這裡的「PRF」就是偽亂數函數,它基於密碼套件裡的最後一個引數,比如這次的 SHA384,通過摘要演演算法來再一次強化「Master Secret」的隨機性。主金鑰有 48 位元組,但它也不是最終用於通訊的對談金鑰,還會再用 PRF 擴充套件出更多的金鑰,比如使用者端傳送用的對談金鑰(client_write_key)、伺服器傳送用的對談金鑰(server_write_key)等等,避免只用一個金鑰帶來的安全隱患。

  有了主金鑰和派生的對談金鑰,握手就快結束了。使用者端發一個「Change Cipher Spec」,然後再發一個「Finished」訊息,把之前所有傳送的資料做個摘要,再加密一下,讓伺服器做個驗證。

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

  最後,我們來看最後一部分,怎麼有點奇怪呢?使用者端怎麼在伺服器返回握手結束的訊息之前就傳送HTTP加密資料了呢?

  首先,我們上圖中的握手過程,其實是TLS主流握手過程,這與傳統的握手過程有兩點不同。

  第一,是使用ECDHE實現金鑰交換,而不是RSA,所以會在伺服器端傳送」Server Key Exchange「訊息。

  第二,因為使用了 ECDHE,使用者端可以不用等到伺服器發回「Finished」確認握手完畢,立即就發出 HTTP 報文,省去了一個訊息往返的時間浪費。這個叫「TLS False Start」,意思就是「搶跑」,和「TCP Fast Open」有點像,都是不等連線完全建立就提前發應用資料,提高傳輸的效率。

  所以你看,關鍵就在於用了ECDHE來作為核心引數生成Master Secret。

二、雙向認證

  其實到這裡TLS握手的核心就基本完事了。只不過大家發現一個問題沒有,上圖中,只有伺服器傳了Certificate,讓使用者端驗證伺服器的身份。而伺服器並沒有驗證使用者端的身份。這是因為通常單向認證通過後已經建立了安全通訊,用賬號、密碼等簡單的手段就能夠確認使用者的真實身份。

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

  熟悉不?現在知道為啥你之前去銀行的時候,銀行會給你個U盾,在網站上操作轉賬啥的時候,都必須插上U盾才行,現在知道這個U盾是用來幹啥的了吧?就是給你原生的電腦安裝證書。當然,現在好像基本上不用U盾了,直接從可信的網站上下載證書就可以了。

  雙向認證的流程也沒有太多變化,只是在「Server Hello Done」之後,「Client Key Exchange」之前,使用者端要傳送「Client Certificate」訊息,伺服器收到後也把證書鏈走一遍,驗證使用者端的身份。大家可以參照本章的圖,自己理解一下噢~

三、小結

  本篇,很重要,還有點複雜。大家要熟悉一下本章的兩張握手圖,理解TLS的握手過程。這個總結好像有點糊弄~~哈哈哈

  哦對,大家還可以在Chrome瀏覽器裡的Security中檢視HTTPS的相關資訊。