幾幅圖,拿下 HTTPS
2021-01-13 21:00:12 軟體

我很早之前寫過一篇關於 HTTP 和 HTTPS 的文章,但對於 HTTPS 介紹還不夠詳細,只講了比較基礎的部分,所以這次我們再來深入一下 HTTPS,用實戰抓包的方式,帶大家再來窺探一次 HTTPS。

對於還不知道對稱加密和非對稱加密的同學,你先複習我以前的這篇文章「硬核!30 張圖解 HTTP 常見的面試題」,本篇文章預設大家已經具備了這些知識。


TLS 握手過程

HTTP 由於是明文傳輸,所謂的明文,就是說使用者端與伺服器端通訊的資訊都是肉眼可見的,隨意使用一個抓包工具都可以截獲通訊的內容。

所以安全上存在以下三個風險:

  • 竊聽風險,比如通訊鏈路上可以獲取通訊內容,使用者號容易沒。
  • 篡改風險,比如強制植入垃圾廣告,視覺汙染,使用者眼容易瞎。
  • 冒充風險,比如冒充淘寶網站,使用者錢容易沒。

HTTPS 在 HTTP 與 TCP 層之間加入了 TLS 協定,來解決上述的風險。

TLS 協定是如何解決 HTTP 的風險的呢?

  • 資訊加密: HTTP 互動資訊是被加密的,第三方就無法被竊取;
  • 校驗機制:校驗資訊傳輸過程中是否有被第三方篡改過,如果被篡改過,則會有警告提示;
  • 身份證書:證明淘寶是真的淘寶網;

可見,有了 TLS 協定,能保證 HTTP 通訊是安全的了,那麼在進行 HTTP 通訊前,需要先進行 TLS 握手。TLS 的握手過程,如下圖:

上圖簡要概述來 TLS 的握手過程,其中每一個「框」都是一個記錄(record),記錄是 TLS 收發資料的基本單位,類似於 TCP 裡的 segment。多個記錄可以組合成一個 TCP 包傳送,所以通常經過「四個訊息」就可以完成 TLS 握手,也就是需要 2個 RTT 的時延,然後就可以在安全的通訊環境裡傳送 HTTP 報文,實現 HTTPS 協定。

所以可以發現,HTTPS 是應用層協定,需要先完成 TCP 連線建立,然後走 TLS 握手過程後,才能建立通訊安全的連線。

事實上,不同的金鑰交換演演算法,TLS 的握手過程可能會有一些區別。

這裡先簡單介紹下金鑰交換演演算法,因為考慮到效能的問題,所以雙方在加密應用資訊時使用的是對稱加密金鑰,而對稱加密金鑰是不能被洩漏的,為了保證對稱加密金鑰的安全性,所以使用非對稱加密的方式來保護對稱加密金鑰的協商,這個工作就是金鑰交換演演算法負責的。

接下來,我們就以最簡單的 RSA 金鑰交換演演算法,來看看它的 TLS 握手過程。


RSA 握手過程

傳統的 TLS 握手基本都是使用 RSA 演演算法來實現金鑰交換的,在將 TLS 證書部署伺服器端時,證書檔案中包含一對公私鑰,其中公鑰會在 TLS 握手階段傳遞給使用者端,私鑰則一直留在伺服器端,一定要確保私鑰不能被竊取。

在 RSA 金鑰協商演演算法中,使用者端會生成隨機金鑰,並使用伺服器端的公鑰加密後再傳給伺服器端。根據非對稱加密演演算法,公鑰加密的訊息僅能通過私鑰解密,這樣伺服器端解密後,雙方就得到了相同的金鑰,再用它加密應用訊息。

我用 Wireshark 工具抓了用 RSA 金鑰交換的 TLS 握手過程,你可以從下面看到,一共經歷來四次握手:

對應 Wireshark 的抓包,我也畫了一幅圖,你可以從下圖很清晰地看到該過程:

那麼,接下來針對每一個 TLS 握手做進一步的介紹。

TLS 第一次握手

使用者端首先會發一個「Client Hello」訊息,字面意思我們也能理解到,這是跟伺服器「打招呼」。

訊息裡面有使用者端使用的 TLS 版本號、支援的密碼套件列表,以及生成的亂數(Client Random,這個亂數會被伺服器端保留,它是生成對稱加密金鑰的材料之一。

TLS 第二次握手

當伺服器端收到使用者端的「Client Hello」訊息後,會確認 TLS 版本號是否支援,和從密碼套件列表中選擇一個密碼套件,以及生成亂數(Server Random

接著,返回「Server Hello」訊息,訊息裡面有伺服器確認的 TLS 版本號,也給出了亂數(Server Random),然後從使用者端的密碼套件列表選擇了一個合適的密碼套件。

可以看到,伺服器端選擇的密碼套件是 「Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256」。

這個密碼套件看起來真讓人頭暈,好一大串,但是其實它是有固定格式和規範的。基本的形式是「金鑰交換演演算法 + 簽名演演算法 + 對稱加密演演算法 + 摘要演演算法」, 一般 WITH 單詞前面有兩個單詞,第一個單詞是約定金鑰交換的演演算法,第二個單詞是約定證書的驗證演演算法。比如剛才的密碼套件的意思就是:

  • 由於 WITH 單詞只有一個 RSA,則說明握手時金鑰交換演演算法和簽名演演算法都是使用 RSA;
  • 握手後的通訊使用 AES 對稱演演算法,金鑰長度 128 位,分組模式是 GCM;
  • 摘要演演算法 SHA384 用於訊息認證和產生亂數;

就前面這兩個使用者端和伺服器端相互「打招呼」的過程,使用者端和伺服器端就已確認了 TLS 版本和使用的密碼套件,而且你可能發現使用者端和伺服器端都會各自生成一個亂數,並且還會把亂數傳遞給對方。

那這個亂數有啥用呢?其實這兩個亂數是後續作為生成「對談金鑰」的條件,所謂的對談金鑰就是資料傳輸時,所使用的對稱加密金鑰。

然後,伺服器端為了證明自己的身份,會傳送「Server Certificate」給使用者端,這個訊息裡含有數位憑證。

隨後,伺服器端發了「Server Hello Done」訊息,目的是告訴使用者端,我已經把該給你的東西都給你了,本次打招呼完畢。

使用者端驗證證書

在這裡剎個車,使用者端拿到了伺服器端的數位憑證後,要怎麼校驗該數位憑證是真實有效的呢?

數位憑證和 CA 機構

在說校驗數位憑證是否可信的過程前,我們先來看看數位憑證是什麼,一個數位憑證通常包含了:

  • 公鑰;
  • 持有者資訊;
  • 證書認證機構(CA)的資訊;
  • CA 對這份檔案的數位簽章及使用的演演算法;
  • 證書有效期;
  • 還有一些其他額外資訊;

那數位憑證的作用,是用來認證公鑰持有者的身份,以防止第三方進行冒充。說簡單些,證書就是用來告訴使用者端,該伺服器端是否是合法的,因為只有證書合法,才代表伺服器端身份是可信的。

我們用證書來認證公鑰持有者的身份(伺服器端的身份),那證書又是怎麼來的?又該怎麼認證證書呢?

為了讓伺服器端的公鑰被大家信任,伺服器端的證書都是由 CA (Certificate Authority,證書認證機構)簽名的,CA 就是網路世界裡的公安局、公證中心,具有極高的可信度,所以由它來給各個公鑰簽名,信任的一方簽發的證書,那必然證書也是被信任的。

之所以要簽名,是因為簽名的作用可以避免中間人在獲取證書時對證書內容的篡改。

數位憑證簽發和驗證流程

如下圖圖所示,為數位憑證簽發和驗證流程:

CA 簽發證書的過程,如上圖左邊部分:

  • 首先 CA 會把持有者的公鑰、用途、頒發者、有效時間等資訊打成一個包,然後對這些資訊進行 Hash 計算,得到一個 Hash 值;
  • 然後 CA 會使用自己的私鑰將該 Hash 值加密,生成 Certificate Signature,也就是 CA 對證書做了簽名;
  • 最後將 Certificate Signature 新增在檔案證書上,形成數位憑證;

使用者端校驗伺服器端的數位憑證的過程,如上圖右邊部分:

  • 首先使用者端會使用同樣的 Hash 演演算法獲取該證書的 Hash 值 H1;
  • 通常瀏覽器和作業系統中整合了 CA 的公鑰資訊,瀏覽器收到證書後可以使用 CA 的公鑰解密 Certificate Signature 內容,得到一個 Hash 值 H2 ;
  • 最後比較 H1 和 H2,如果值相同,則為可信賴的證書,否則則認為證書不可信。
證書鏈

但事實上,證書的驗證過程中還存在一個證書信任鏈的問題,因為我們向 CA 申請的證書一般不是根證書籤發的,而是由中間證書籤發的,比如百度的證書,從下圖你可以看到,證書的層級有三級:

對於這種三級層級關係的證書的驗證過程如下:

  • 使用者端收到 baidu.com 的證書後,發現這個證書的簽發者不是根證書,就無法根據本地已有的根證書中的公鑰去驗證 baidu.com 證書是否可信。於是,使用者端根據 baidu.com 證書中的簽發者,找到該證書的頒發機構是 「GlobalSign Organization Validation CA - SHA256 - G2」,然後向 CA 請求該中間證書。
  • 請求到證書後發現 「GlobalSign Organization Validation CA - SHA256 - G2」 證書是由 「GlobalSign Root CA」 簽發的,由於 「GlobalSign Root CA」 沒有再上級簽發機構,說明它是根證書,也就是自簽證書。應用軟體會檢查此證書有否已預載於根證書清單上,如果有,則可以利用根證書中的公鑰去驗證 「GlobalSign Organization Validation CA - SHA256 - G2」 證書,如果發現驗證通過,就認為該中間證書是可信的。
  • 「GlobalSign Organization Validation CA - SHA256 - G2」 證書被信任後,可以使用 「GlobalSign Organization Validation CA - SHA256 - G2」 證書中的公鑰去驗證 baidu.com 證書的可信性,如果驗證通過,就可以信任 baidu.com 證書。

在這四個步驟中,最開始使用者端只信任根證書 GlobalSign Root CA 證書的,然後 「GlobalSign Root CA」 證書信任 「GlobalSign Organization Validation CA - SHA256 - G2」 證書,而 「GlobalSign Organization Validation CA - SHA256 - G2」 證書又信任 baidu.com 證書,於是使用者端也信任 baidu.com 證書。

總括來說,由於使用者信任 GlobalSign,所以由 GlobalSign 所擔保的 baidu.com 可以被信任,另外由於使用者信任作業系統或瀏覽器的軟體商,所以由軟體商預載了根證書的 GlobalSign 都可被信任。

作業系統裡一般都會內建一些根證書,比如我的 MAC 電腦裡內建的根證書有這麼多:

這樣的一層層地驗證就構成了一條信任鏈路,整個證書信任鏈驗證流程如下圖所示:

最後一個問題,為什麼需要證書鏈這麼麻煩的流程?Root CA 為什麼不直接頒發證書,而是要搞那麼多中間層級呢?

這是為了確保根證書的絕對安全性,將根證書隔離地越嚴格越好,不然根證書如果失守了,那麼整個信任鏈都會有問題。

TLS 第三次握手

使用者端驗證完證書後,認為可信則繼續往下走。接著,使用者端就會生成一個新的亂數 (pre-master),用伺服器的 RSA 公鑰加密該亂數,通過「Change Cipher Key Exchange」訊息傳給伺服器端。

伺服器端收到後,用 RSA 私鑰解密,得到使用者端發來的亂數 (pre-master)。

至此,使用者端和伺服器端雙方都共用了三個亂數,分別是 Client Random、Server Random、pre-master

於是,雙方根據已經得到的三個亂數,生成對談金鑰(Master Secret),它是對稱金鑰,用於對後續的 HTTP 請求/響應的資料加解密。

生成完對談金鑰後,然後使用者端發一個「Change Cipher Spec」,告訴伺服器端開始使用加密方式傳送訊息。

然後,使用者端再發一個「Encrypted Handshake Message(Finishd)」訊息,把之前所有傳送的資料做個摘要,再用對談金鑰(master secret)加密一下,讓伺服器做個驗證,驗證加密通訊是否可用和之前握手資訊是否有被中途篡改過。

可以發現,「Change Cipher Spec」之前傳輸的 TLS 握手資料都是明文,之後都是對稱金鑰加密的密文。

TLS 第四次握手

伺服器也是同樣的操作,發「Change Cipher Spec」和「Encrypted Handshake Message」訊息,如果雙方都驗證加密和解密沒問題,那麼握手正式完成。

最後,就用「對談金鑰」加解密 HTTP 請求和響應了。


RSA 演演算法的缺陷

使用 RSA 金鑰協商演演算法的最大問題是不支援前向保密。因為使用者端傳遞亂數(用於生成對稱加密金鑰的條件之一)給伺服器端時使用的是公鑰加密的,伺服器端收到到後,會用私鑰解密得到亂數。所以一旦伺服器端的私鑰洩漏了,過去被第三方截獲的所有 TLS 通訊密文都會被破解。

為了解決這一問題,於是就有了 DH 金鑰協商演演算法,這裡簡單介紹它的工作流程。

使用者端和伺服器端各自會生成亂數,並以此作為私鑰,然後根據公開的 DH 計算公示算出各自的公鑰,通過 TLS 握手雙方交換各自的公鑰,這樣雙方都有自己的私鑰和對方的公鑰,然後雙方根據各自持有的材料算出一個亂數,這個亂數的值雙方都是一樣的,這就可以作為後續對稱加密時使用的金鑰。

DH 金鑰交換過程中,即使第三方截獲了 TLS 握手階段傳遞的公鑰,在不知道的私鑰的情況下,也是無法計算出金鑰的,而且每一次對稱加密金鑰都是實時生成的,實現前向保密

但因為 DH 演演算法的計算效率問題,後面出現了 ECDHE 金鑰協商演演算法,我們現在大多數網站使用的正是 ECDHE 金鑰協商演演算法,關於 ECDHE 握手的過程,將在下一篇揭曉,盡情期待哦。