之前寫過一篇講 HTTPS 的思想的文章。

破玩意 | 用 HTTPS 傳紙條

後來又寫了篇用更凝練的語言總體描述了 HTTPS 的主幹。

叮咚 | HTTPS 的分支和主幹

想必通過這兩篇文章,HTTPS 為什麼要這麼設計,以及它是用來解決什麼問題的,大家已經心中有數了。

那接下來就是細節了。

由於之前已經把思想講過了,本篇就通過抓包的方式,專注於 HTTPS 的過程,我會像個無情的流水賬一樣,給大家把一個 HTTPS 的包挖乾淨。

先普及一個知識點,HTTPS = TLS + HTTP。

HTTP 我們很熟悉了,所以我們想知道的 HTTPS 的知識,本質上是想知道 TLS 協議的規範,以及為什麼這樣設計。所以我們本文展開講解 TLSv1.2 協議的內容。

我們開始吧!

直接 postman 發起一個 HTTPS 的 POST 請求,這個 IP 是微博首頁。

wireshark 抓包,過濾出 tls 協議的包,看到如下結果。

一下就可以看到整個 HTTPS 握手的過程了。

這個抓包資料可以加我好友,朋友圈有下載連結。但其實你自己隨便訪問一個網站用 wireshark 抓一下也行。

學一個協議,最科學也是最方便的辦法,就是看官方文件。

我們看 TLS 1.2 的官方文件,RFC-5246,其中 section-7.3 為我們描繪了整個握手過程。

由於我們只是客戶端驗證服務端證書,而沒有服務端驗證客戶端證書的過程,所以我們的包裡,是不包含 Server CertificateRequest(請求客戶端證書)、Client Certificate(客戶端證書)、CertificateVerify(客戶端證書有效性驗證)這三項的。

下面我們一個一個過程拆開來看。

ClientHello

當 client 連線到一個 server 時,第一個傳送的包就是它。

具體包含以下內容:

client_version

客戶端希望的,也是客戶端能支援的最高的 TLS 版本號,這裡是 TLS 1.2 版本。

random

由客戶端生成的隨機數,之後會用到,我們稱之為隨機數 1。

ee8880e816ac14ca5b69bde656c188f37a08bcf2052a550b7867b041f6c1ab48

session_id

用於複用 TLS 連線,防止資源的浪費。但這個要服務端支援才行。

cipher_suites

客戶端支援的密碼學套件,按客戶端偏好排序,如果服務端沒有可支援的,那就回應錯誤(returns a handshake failure alert)並關閉連線。

本次一共發了 18 個密碼學套件

我們拿其中一個密碼學套件舉例

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

我們知道,HTTPS 的原理就是用非對稱加密的方式來交換祕鑰,用對稱加密的方式來通訊,然後裡面夾雜著雜湊演算法用於驗證簽名等。

所以這個密碼學套件就包含了這三個部分。

ECDHE_RSA 指的是祕鑰協商演算法AES_128_GCM 是最終通訊的對稱加密演算法SHA256 是雜湊演算法

以此來確定整個握手過程所需要的演算法都用什麼。

compression_methods

壓縮演算法

extensions

擴充套件欄位

由此看出,本次 clientHello 最重要的資訊就倆,一個隨機數,一組支援的密碼學套件。

接著往下看

ServerHello

服務端給客戶端傳送的包,響應 ClientHello。

具體包含以下內容:

server_version

TLS 的版本,具體是服務端支援的最高版本以及客戶端支援的最低版本。

random

服務端生成的隨機數,且生成規則不能依賴於客戶端的隨機數,我們稱為隨機數 2。

3ad03af5b8a5ebfe7902a250406b2e99d2667e37e524e0e5c333c0e0b9a637e8

session_id

服務端返回的會話 ID。

如果客戶端剛剛發過來的 session_id 服務端已經有了快取,並且同意複用連線,則返回一個和客戶端剛剛發來的相同的 session_id。

也可以傳送一個新的 session_id,以便客戶端下次將其攜帶並且複用。

也可以回覆一個空值,表示不快取 session_id,因此也不會複用。

cipher_suite

選擇的加密套件。

剛剛客戶端傳來 18 個加密套件,服務端選擇了一個迴應,此處迴應的是。

0xc02f

表示

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

這個剛剛解釋過了,表示用 ECDHE_RSA 作為祕鑰交換演算法,用 AES_128 作為通訊時的對稱加密演算法,用 SHA256 作為雜湊演算法。

compression_method

選擇的壓縮演算法,同上

extensions

擴充套件欄位,同上

由此看出,本次 serverHello 最重要的資訊也是倆,和上面的 clientHello 一樣,也是一個隨機數,還有一個從客戶端發來的一組密碼學套件中選擇的一個。

至此,服務端和客戶端都擁有了隨機數 1 和隨機數 2,並且選定了共同確定的密碼學套件。

雙方互相 hello 的過程就此結束了。

接著往下看

Server Certificate

服務端發完上面的 ServerHello 後立即發這個包,這個包非常簡單。

只有一個 Certificates 結構體,就是我們常說的證書。

而後綴還加了個 s,因此翻譯成證書鏈,是由一組證書組成的,最上面的是服務端本身的證書。

我們把這個服務端的證書的關鍵資訊都展開看一下:

這個證書在預設情況下都是 X.509 格式的,除非明確協商說明。

別懷疑,RFC-5246 中就是這樣寫的。

這種格式的結構定義在 RFC-1422 的 3.3 節中有說明。

別廢話了,展開講一下吧。

version

證書版本號,v3

serial number

證書序列號,這個每個頒發機構是唯一的,此處為:

0x0de81066db219caef5ecb01ba273cad1

signature

簽名演算法,僅僅是一個演算法喲。

此處是 1.2.840.113549.1.1.11

表示 sha256WithRSAEncryption

這就表示用 sha256 這個雜湊演算法對證書進行雜湊生成摘要,然後再用 RSA 這個非對稱加密演算法,用 CA 的私鑰加密剛剛生成的摘要,形成數字簽名。

issuer name

頒發者資訊,我們展開看一下

RDNSequence item: 1 item (id-at-countryName=US)
RDNSequence item: 1 item (id-at-organizationName=DigiCert Inc)
RDNSequence item: 1 item (id-at-organizationalUnitName=www.digicert.com)
RDNSequence item: 1 item (id-at-commonName=GeoTrust CN RSA CA G1)

validity period

證書有效期,比如本案例中的

notBefore: utcTime (0)
    utcTime: 20-06-09 00:00:00 (UTC)
notAfter: utcTime (0)
    utcTime: 22-05-15 12:00:00 (UTC)

subject name

證書持有者資訊

rdnSequence: 5 items
    RDNSequence item: 1 item (id-at-countryName=CN)
        RelativeDistinguishedName item (id-at-countryName=CN)
            Id: 2.5.4.6 (id-at-countryName)
            CountryName: CN
    RDNSequence item: 1 item (id-at-stateOrProvinceName=Beijing)
        RelativeDistinguishedName item (id-at-stateOrProvinceName=Beijing)
            Id: 2.5.4.8 (id-at-stateOrProvinceName)
            DirectoryString: printableString (1)
    RDNSequence item: 1 item (id-at-organizationName=Sina.com Technology(China)Co.,ltd)
        RelativeDistinguishedName item (id-at-organizationName=Sina.com Technology(China)Co.,ltd)
            Id: 2.5.4.10 (id-at-organizationName)
            DirectoryString: printableString (1)
    RDNSequence item: 1 item (id-at-organizationalUnitName=Sina.com Technology(China)Co.,ltd)
        RelativeDistinguishedName item (id-at-organizationalUnitName=Sina.com Technology(China)Co.,ltd)
            Id: 2.5.4.11 (id-at-organizationalUnitName)
            DirectoryString: printableString (1)
    RDNSequence item: 1 item (id-at-commonName=weibo.cn)
        RelativeDistinguishedName item (id-at-commonName=weibo.cn)
            Id: 2.5.4.3 (id-at-commonName)
            DirectoryString: printableString (1)
                printableString: weibo.cn

太亂了,簡化下就是

countryName=CN
stateOrProvinceName=Beijing
organizationName=Sina.com Technology(China)Co.,ltd
organizationalUnitName=Sina.com Technology(China)Co.,ltd
commonName=weibo.cn

顧名思義,就是微博網站這個頒發機構的資訊

subject public key

證書的公鑰資訊,本案例中是:

3082010a0282010100c4c84ff479214c5875037500cfc453d676cec0e64c7ab5f14e0284d8b49b6f23ec70f853d38eb60dc91a6fa826d49d188fd20158c3aaa101b4b6a0c89d4df824fe755ff2cfd4f876bb2dcefe760d6f9ec5e9e2990cab4367949f27062857ca26f2303f07f6c6c953f382cb8a379ae7b28c6234983fd61739550dc6b502c4feb9c9991459265f61471b91e3b592ad3e21a276d14321f462c820477e2b34a7ea16da1f3ffa760d9065ceb5a98ffc3d19da519133d542f74dd70c4366d98d16c36c27e4384cf31130614a1398621c64c260ad91d0de32900e2ac2589029b35d21eacd078bea5cb0a9db4bc3b7ba644d0459c2e0489ae62215cc525c36784191d94b0203010001

除此之外,還剩下兩項,證書籤名演算法(誒?這個剛剛不是已經傳過了麼),以及證書的簽名值

從中可以讀出,簽名演算法就是 sha256WithRSAEncryption,簽名值我提取出來,如下:

220f0a0d15fa3c3909bb1e9f4d1d78cb9a41983cc9e549a4f8781f483fc18c679421eb84875354e00d86877eb1e80fb691eb0133208a0bee641ca0a7585b0e85818e88557a50f3f6241eebbc9cf49be40dc21f1d82a0cf30de30643cf236b290e74b6dee9bfdb71dab5f03b5cfd965bc16e139bc66f37119fcfc73aaf4c50fda1111bd948f507f85dd239012be73c953234328e332091c2fa38c482b6b4fdba52a26a1cc557a9c95edacea1d7b62f8996c934a5b3d762dd4f3cb88d405b805b7f604c07bd518665940f34fcb9e54121ac724a1ea3a58f42c9556f25058b19afa8c233fdf881bfeff32186051ec104fa23d4024b16b672f8eb33e359c3f813aa1

至於它是怎麼算出來的,之前也畫過一張圖,我就直接放過來了(注意,這裡的給服務端,是指 CA 機構給服務端,然後服務端現在又給了客戶端)。

還記得剛剛的簽名演算法麼?sha256WithRSAEncryption

這個圖裡的雜湊摘要用的演算法就是 sha256,而 CA 私鑰加密用的演算法就是 RSA。

好了,全部證書相關的資訊就講完了,同時也是 Server Certificate 這個環節的唯一資訊。

證書一方面可以通過服務端給客戶端傳遞的包解析來看,另一方面,由於瀏覽器要解析這個證書資訊做驗證,所以通常瀏覽器有更直觀的方式可以檢視,就不用我們費心思了。

點開瀏覽器位址列的小鎖頭。

看,和我們剛剛抓包分析出來的資訊,一毛一樣。

我們繼續看下一個包。

我相信你已經不記得整個流程到哪裡了,好心的我給你放之前的圖。

剛剛進行完兩個 hello,以及一個傳遞證書的包 Certificate,接下來就要進行協商對稱祕鑰的過程了。

這個過程,最簡單就是 RSA 演算法,用服務端公鑰直接加密客戶端隨機生成的一個對稱加密祕鑰,發給服務端。

但現在基本上都用更為複雜的祕鑰交換演算法,我們往下看。

Server Key Exchange

用於 premaster secret 生成的

之前說了,祕鑰交換演算法是 ECDHE 演算法,這裡隱含著包含了好多資訊。

首先選擇的橢圓曲線是 named_curve 型別,並指定了基點生成一個私鑰,這個我們抓包看不見根據私鑰和基點,計算出公鑰,然後把這個公鑰用服務端公鑰加密,傳送給服務端,這個我們能看到,就是裡面的 Pubkey ,值為

2ce174dbdb6f481b6ab9fd37446dca95b6ade3613afba03243d163360f63713b

至於 ECDHE 用到的橢圓曲線祕鑰交換演算法的細節,這裡就不展開講了,因為我也不會,就知道它最終是為了和服務端協商出來一個 premaster_secret 就好。

接著往下看。

Server CertificateRequest

請求客戶端證書,此案例中沒有,一般銀行等需要客戶端也加密的才有,比如 U 盾。

Server ServerHelloDone

標識著 serverHello 這個握手過程結束了。

Client Certificate

客戶端證書。本案例中沒有,也說明了上面服務端確實沒有傳送 CertificateRequest

Client ClientKeyExchange

緊接著 ServerHelloDone 傳送,用於協商出 premaster_secret,同之前的 ServerKeyExchange 配合使用的。

這回輪到客戶端給服務端一個用於 ECDHE 演算法的公鑰了。

f04e0743377afb5e9bf0a84aec5c7257957b85daee98fc48fb8971a26b457077

而同時客戶端與這個公鑰配對的私鑰,我們也無法通過抓包看出來。

生成最終通訊的對稱加密祕鑰
master_secret

這一步不是抓包的資訊,而是客戶端和服務端此時都在自己端內所做的事情,非常關鍵。

就是計算出最終對稱加密用的祕鑰 master_secret,這也是整個花裡胡哨的過程,最終且唯一的一個目的,並且兩端算出來的結果肯定是一樣的。

HTTPS 的目的,不就是雙方協商出一個共同的對稱加密祕鑰麼,怕被中間人攔截到,所以做的證書呀,非對稱加密演算法呀,祕鑰協商演算法等複雜的規定。

那 master_secret 是怎麼計算出來的呢?

還記不記得之前我們得到了三個隨機數:

隨機數 1(客戶端隨機數) :在 ClientHello 訊息裡,由客戶端生成的隨機數,是 ee8880e816ac14ca5b69bde656c188f37a08bcf2052a550b7867b041f6c1ab48隨機數 2(服務端隨機數) :在 ServerHello 訊息裡,由服務端生成的隨機數,是3ad03af5b8a5ebfe7902a250406b2e99d2667e37e524e0e5c333c0e0b9a637e8隨機數 3(pre_master) :通過祕鑰交換演算法 ECDHE 計算出的,我們叫它 pre_master。

最終的對稱加密祕鑰 master_secret,就是根據這三個隨機數共同計算出來的。

一旦雙方協商出來了這個相同的對稱祕鑰,那就可以開始愉快地安全通訊了,TLS 層的工作也就圓滿完成。

所以可想而知,接下來的工作,就都是收尾工作了,因為祕鑰已經協商好了。

Client CertificateVerify

驗證客戶端證書有效性,本案例中沒有。

Client ChangeCipherSpec

祕鑰改變通知,此時客戶端已經生成了 master_secret,之後的訊息將都通過 master secret 來加密。

可以看到,就是個標識,沒有攜帶什麼有用的資訊。

Client Finish

這一步對應的是 Client Finish 訊息,客戶端將前面的握手訊息生成摘要再用協商好的祕鑰加密,這是客戶端發出的第一條加密訊息。服務端接收後會用祕鑰解密,能解出來說明前面協商出來的祕鑰是一致的。

Server ChangeCipherSpec

也是祕鑰改變通知,此時服務端也已經生成了 master_secret 了,後面的通訊都用此值加密。

Server Finish

同 Client Finish,伺服器端傳送握手結束通知,同時會帶上前面所發內容的簽名到客戶端,保證前面通訊資料的正確性。

Application Data

之後就是真正加密的資料了。

總結

我們去掉客戶端證書這個部分,整個過程簡化來說一遍。1. client --> server ClientHello客戶端生成隨機數,併發送一組密碼學套件供服務端選
2. server--> client ServerHello服務端生成隨機數,並從上述密碼學套件組裡選一個3. server--> client Certificate服務端發給客戶端證書
4. server--> client ServerKeyExchange服務端發給客戶端祕鑰交換演算法所需的值
5. server--> client ServerHelloDone服務端 hello 階段結束
6. client --> server ClientKeyExchange客戶端發給服務端祕鑰交換演算法所需的值7. client --> server ChangeCipherSpec客戶端告訴服務端,我已經知道祕鑰了,之後的訊息我就都加密傳送了。
8. client --> server Finish結束並驗證
7. server --> server ChangeCipherSpec服務端告訴客戶端,我已經知道祕鑰了,之後的訊息我就都加密傳送了。9. server--> client Finish

結束並驗證

如果不看客戶端證書,不看複雜的 premaster_secret 協商演算法,不看壓縮演算法這些細節,其實簡單說只有三大步驟。
首先第一步,客戶端對服務端說 hello,並且發一組密碼套件。第二步,服務端對客戶端說 hello,並且選擇一組密碼套件,同時把附帶公鑰的證書發給客戶端。第三步,客戶端驗證證書,並且把對稱加密的祕鑰用服務端的公鑰加密,發給服務端,服務端用私鑰解密出就是對稱加密祕鑰了。(當然實際情況沒這麼簡單,比如我們本次抓包是用 ECDHE 祕鑰交換演算法,這也是目前大部分網站的做法)。經此三步之後,客戶端與服務端就都擁有了相同的對稱加密祕鑰,進行簡單的收尾工作,也就是通知對方祕鑰已生成好的資訊,之後就可以開始通訊了。最後說一句,本案例的抓包資料可以加我好友,朋友圈有下載連結,當然你也可以自己用 wireshark 抓,隨便訪問一個網站幾乎都是 https 的。

參考資料:

RFC-5246 The Transport Layer Security (TLS) Protocol Version 1.2

RFC-1422 Privacy Enhancement for Internet Electronic Mail Part II: Certificate-Based Key Management