Wireshark 抓包理解 HTTPS 請求流程

目錄

  1. 準備
  2. 分析
    2.1. 三次握手
    2.2. 創建 HTTP 代理(非必要)
    2.3. TLS/SSL 握手
    2.4. 數據傳輸
    2.5. 四次揮手
  3. 擴展
    3.1. Session ID 和 Session Ticket
    3.2. SNI(Server Name Indication)
    3.3. ALPN(Application Layer Protocol Negotiation)
  4. 資料

1. 準備

我的操作是這樣的,讓手機和電腦在同一個局域網內(比如連接同一個 wifi),接著在手機的wifi上設置代理,電腦使用 Charles 做代理,IP 為電腦在局域網 IP,我這邊的環境,手機 IP 為 172.17.32.117,電腦 IP 為 172.17.32.19。再設置代理端口為 8888。設置代理后,接下來手機的請求都會通過電腦的網卡代理請求發送出去。

其實可以不用這么繞。我之所以多設了一個代理,是因為自己電腦創建的 wifi 熱點,手機接收不到。為了讓手機的包能經過電腦網絡嗅探到才這么處理的。

最便捷的方式,就是電腦放個 wifi 熱點給手機連接完事。

創建后代理連接后,然后使用 Wireshark 嗅探網卡,比如我這里使用的是 etho0 網卡去訪問網絡的。這時候玩玩手機,打開幾個請求,Wireshark 上面就會出現捕捉的大量的包,各種各樣的協議都有,有 ARP 尋人啟事(尋找 IP 對應的物理地址),有 TCP 連接包,有 HTTP 請求包。

請求數據

這里我設置了一下過濾規則,把對網易的一個 https://nex.163.com 的一個的請求過濾出來如下:

HTTPS 連接數據

整個完整的 HTTPS 請求的過程如下:

  • TCP 三次握手
  • 因為我使用電腦作為代理,所以還有一個 CONNECT 請求用來建立 HTTP 代理
  • 使用 TLSv1.2 進行 SSL 握手
  • 使用握手協商好的密鑰對 HTTP 進行加密傳輸
  • TCP 四次揮手

接下來把手機稱為 A(172.17.32.211),電腦稱為 B(172.17.32.19),對完整的過程進行簡要分析。

2. 分析

2.1. 三次握手

2.1.1. TCP 協議內容

作為整個過程的第一個 TCP 包,這里對它做一個詳細的剖析,理解一下 TCP 報文的格式和內容。TCP 是傳輸層協議,負責可靠的數據通信,它在整個體系結構的位置如下:

計算機網絡體系結構

作為傳輸層協議,主要為上層協議提供三個功能:

  • 可靠傳輸,為每個字節安排好序號,排好序,并且有重傳機制保證信息不丟失。
  • 流量控制,有滑動窗口,避免發送端和接收端速率不一致導致發包過快來不及接收。
  • 擁塞避免,在網絡環境差的時候,控制好傳包的時間間隔,避開高峰期,不給原本已經很擁堵的網絡添堵。

TCP 協議為 HTTP 和 SSL 協議提供了基礎的通信功能。所以 SSL 協議是基于 TCP 的。

三次握手的內容有:

No. Time Source Destionation Protocol Length Info
379 4.623811 172.17.32.211 172.17.32.19 TCP 74 35973 → 8888 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1 TSval=15986187 TSecr=0 WS=256
380 4.623860 172.17.32.19 172.17.32.211 TCP 74 8888 → 35973 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1 TSval=59355465 TSecr=15986187
393 4.781431 172.17.32.211 172.17.32.19 TCP 66 35973 → 8888 [ACK] Seq=1 Ack=1 Win=87808 Len=0 TSval=15986193 TSecr=59355465

對每個包進行詳細的分析:

2.1.2. Round 1

A 發出一個帶 SYN 同步位的包,通知服務端要建立連接

第一次握手,發出的 TCP 包的數據和 Wireshark 解析的結果如下:

第一次握手

灰色部分就是 TCP 報文的數據內容,第一個兩個字節 0x8c85 = 35973 表示源端口。

TCP 報文的格式如下,對應的如上圖的灰色部分。非灰色部分分別為 IP 首部和數據幀首部。

 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    源端口                      |                     目的端口                   |
+-----------------------------------------------+-----------------------------------------------+
|                                             序列號                                             |
+-----------------------------------------------------------------------------------------------+
|                                             確認號                                             |
+-----------+-----------------+-----------------+-----------------------------------------------+
|  首部長度  |     保留位       | U| A| P| R| S| F|                      窗口                      |
+-----------+-----------------+-----------------+-----------------------------------------------+
|                    校驗和                      |                     緊急指針                   |
+-----------------------------------------------+-----------------------+-----------------------+
|                    選項                                                |        填充            |
+=======================================================================+=======================+
|                                             數據                                               |

參考謝希仁版本的 《計算機網絡》一書,對照著整個報文格式表,把整個 TCP 報文的二進制信息和相關意義做些說明:

  • 源端口,2 字節,0x8c85 = 35973。

  • 目的端口,2字節,0x22b8 = 8888。

  • 序號,4 字節,0xc7a3ce5c。TCP 連接傳送的每一個字節都有編號,而這里表示的是要發送的數據(data)的第一個字節的序號。后面按照這個序號遞增。這里發送的是 TCP 連接的第一個包,這里的序號是隨機產生的。

  • 確認號,4 字節,0x00000000。期望收到的下一個報文段數據部分第一個字節的序號。如果發出了 N 確認號,就表示 N-1 之前的數據都收到了。

  • 首部長度(偏移),4 位,0xa = 10。這里每一位表示4字節,所以 10*4 = 40 字節。TCP 首部的長度,因為 4 位最大的數為 15,所以整個首部最大 60 字節。首部后面就是數據字段。

  • 保留位,6 位,0x000。這里還沒有用到為 0,這里的設計作為一個擴展以便未來會用上。

  • 控制位,6 位,0x002 = 二進制 000010。每一位都有意義,這里對應 6 種類型:

    • URG,Urgent,表示緊急數據要提交,有了這個標記為整個報文就有了插隊的特權,和緊急指針一起用。
    • ACK,Acknowledge,1 表示這是一個確認報文,用來確認收到了包,確認報文是不帶數據的。
    • PSH,Push,1 表示這是一個推送報文,通知對方盡快響應。因為服務端可能因為緩存問題要等了一會兒才發包。這里就是催促一下對方趕緊發包。
    • RST,Reset,1 表示拒絕了這個包,網絡發生錯誤的時候這種包會非常多。比如重復的包就會被 reset 掉。
    • SYN,Synchronization,1 表示建立連接用來同步序號。用來握手階段,通知對方包的初始序號。
    • FIN,Finish,1 表示發送方 B 完成數據發送,通知接收方 A 該結束了。

    我們這里作為整個請求過程中的第一個 TCP 報文,僅僅設置一個控制位 SYN,一方面通知響應者 B 要建立連接了,另一方面同步一下序號。

  • 窗口,2 字節,0xffff = 65535。發送本報文的接收窗口大小,比如 A 發送了這個報文,表示能接收的數據量為從確認號算起來加上窗口大小,B 發送的報文字節數不能超過這個限制。這個值受 A 的緩存影響,是動態變化的。前面給出確認號為 0, 然后窗口大小 65535,表示還有 65535 的緩存空間可以接受序號 0 ~ 65535 的字節。

  • 檢驗和,2 字節,0x068f。接收方受到報文要計算一下數據包的檢驗和是否和該值匹配,確保數據包的完整。這里只保證了數據完整性,并沒有確認服務端身份,也沒有用摘要算法。目的在于能快速檢驗,而且采用檢驗和的方式還可以使用硬件加速。

  • 緊急指針,2 字節,0x0000f。和控制位 URG 配合使用,意義在于,有緊急數據要處理,這里的值表示緊急數據在報文中的位置。

到這里的話,TCP 數據報首部固定部分結束,固定部分一共有 20 字節。也就是 TCP 首部,至少要有 20 字節。

固定首部后,就是可長度可以變化的選項了:

  • 選項,可達 40 字節

    • 最大報文長度,0x020405b4。0x2 表示這是個 MSS 選項,0x04 表示該選項一共有 4 字節,這里的 0x05b4 = 1460 為該選項的值。這個表示 TCP 數據部分的最大字節數。

    • 時間戳,0x080a00f3ee0b00000000。0x8 表示這是個時間戳選項,0x0a 表示該選項一共有 10 字節,0x00f3ee = 15986187 就是發送者的發送時間,0x00000000 = 0 表示接收端的時間。

整個所以 TCP 數據包的大小可以這樣表示:

+------------------------+--------------------------------------------------------------+
|  TCP 首部,20 ~ 60 字節 |           數據部分,受 MSS 大小限制                           |     
+------------------------+--------------------------------------------------------------+

我們 Wireshark 后面的一長串的信息就指出了該 TCP 報文的一些主要信息:

35973 → 8888 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1 TSval=15986187 TSecr=0 WS=256
  • 源端口和目的端口,35973->8888。
  • 序號,Seq=0,這是一個相對值而非絕對值,相對第一個包的序列號。因為是整個 TCP 流的第一包,所以 Wireshark 認定該包的序列號為 0。
  • 窗口大小,win=65535,也就是發送端的當前窗口最多容納 65535 個字節。
  • 數據部分大小,Len=0,不帶數據。
  • 最大報文大小選擇,MSS=1460,數據部分最多有 1460 個字節。
  • 選擇確認選項,SACK_PERM=1。
  • 發送時間戳,TSval=15986187,發出這個數據包的時候的時間戳。
  • 應答時間戳,TSecr=0,當前要發送的包應答的那個包的發送時間戳,因為是第一個包,應答的時間戳為 0。
  • 窗口擴大,WS=256。

從上面的分析可以看出,這個 SYN 包并沒有攜帶數據,但是按協議這里要消耗一個序號。

在發出 SYN 包后,A 端進入 SYN-SENT 狀態。

2.1.3. Round 2

B 收到 SYN 包,發出 SYN + ACK 確認包

這個包,既是確認收到了第一次握手的包,也是一個由 B 端發出的同步包,表示自己準備好了,可以開始傳數據了。

因為 Wireshark 已經幫我們分析好包的內容了,上面列舉的包二進制數據和 TCP 報文結構只是為了學習,實際應用可以直接看 Wireshark 的解析內容。包的內容如下:
第二次握手

TCP 報文包相對于第一次握手的包可以窺見一些變化:

  • 源端口和目的端口:8888 -> 35973。

  • 窗口大小:8192,可以看成接收端的窗口只有 8192,和發送端差距還是挺大的。

  • 控制位:既有 SYN 又有 ACK。因為這既是一個接收端 B 的自己同步包,里面有一個接收端的初始序號,Wireshark 轉化為相對序號 0;同時這也是對第一次握手的包的確認。因此這個包也不帶數據。

  • 發送時間戳:59355465

  • 應答時間戳:15986187

可以看到,這個包的應答時間戳剛好是第一次握手的發送時間戳。從這里也可以理解到,這個包就是在響應第一次握手的包

所以,接收方 A 可以利用這個值來計算這一次 RTT,收到第二次握手的包后,計算當前時間戳減去該包的應答時間戳就是一個 RTT 的延時了。

這雖然是 ACK 包,但也是 SYN 包,所以也要消耗一個序號。

在發出這個包后,B 端進入 SYN-REVD 狀態。

2.1.4. Round 3

A 收到后,再發出一個 ACK 確認包

發出的包如下:

No. Time Source Destionation Protocol Length Info
393 4.781431 172.17.32.211 172.17.32.19 TCP 66 35973 → 8888 [ACK] Seq=1 Ack=1 Win=87808 Len=0 TSval=15986193 TSecr=59355465

這里我們產生一個疑問,這里發送端 A 發連接請求信息、接收端 B 發確認信息,又互相同步了序號,是不是已經可以傳輸數據了?但實際上 A 還要再發一個 ACK 確認報文,如圖所示,確認收到了 B 第二次握手發出的包,這個時候,在這個 ACK 包后 A 和 B 才正式進入 ESTABLISHED 狀態。這就是第三次握手。

這是為什么呢?

假設我們用兩次握手,然后在第一次握手期間,A 發了第一次握手包后出現了這樣的場景:一直沒有得到響應而進行超時重傳,又發了一次包,然后我們稱上一次包為失效包。

然后我們可以看到:

  • 新包到達接收端 B,然后因為兩次握手 B 覺得連接建立,于是等著發送端 A 發數據,A 也發了數據。
  • 失效的包經過艱苦跋涉,也到達了接收端 B,B 并不清楚這是失效包,又開始等待發送端 A 發數據。然而此時 A 不發數據,所以 B 的資源就浪費掉了。

所以,只有接收端 B 在發送端 A 發出了第三次握手包后,才認為連接已經建立,開始等待發送端 A 發送的數據,才不會因為失效的連接請求報文導致接收端異常。

2.1.5. 小結

TCP 三次握手的時序圖如下:

三次握手時序圖

三次握手,有幾個重要的任務,一個是同步序號,接收端和發送端都發出同步包來通知對方初始序號,這樣子后面接收的包就可以根據序號來保證可靠傳輸;另一個是讓發送端和接收都做好準備。然后就開始傳數據了。

整個過程都發生在 HTTP 報文發出之前。HTTP 協議就是依靠著 TCP 協議來做傳輸的管理。TCP 可以認為是它的管家,管理著傳輸的大大小小的事務,比如要不要保證包順序一致?什么時候發包?要不要收包?TCP 是很嚴格的。

三次握手在 Java API 層面,對應的就是 Socket 的連接的創建(最終調用的是 native 層的 socket 創建):

socket.connect(address, connectTimeout);

這里的 connectTimeout 對應的是三次握手的總時長,如果超時了就會被認為連接失敗。

比如一個場景,客戶端發出一個 SYN 報文后,遲遲沒有收到服務端的 SYN + ACK。這時候客戶端觸發重傳機制,每次重傳的間隔時間加倍,同樣沒有收到包。然后如果這段時間超出了連接超時時間的設置,那么建立連接超時就發生了。

所以,如果三次握手要花的時間,總是大于這里的 connectTimeout 時間,這個 Socket 就無法建立連接。

我們這一次請求的三次握手時間在 180ms 左右。

像在 OkHttp 中,如果是三次握手階段的連接超時,是會有重試機制的。也就是重新建聯,重新發出 SYN 報文發起 TCP 連接。重新建聯的時候會更換連接的路由,如果已經沒有可選擇路由的話,那么這個就真的失敗了。

在 OkHttp 3.9.0 的默認配置中,連接超時的時間為 10000ms = 10s。在 OkHttpClient.Builder 中。

connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;

實際應用的時候,根據業務場景來調整。

2.2. 創建 HTTP 代理(非必要)

這次請求,為了讓 Wireshark 抓到手機的包,我使用了電腦作為代理。

其實就是客戶端 A 使用 HTTP 協議和代理服務器 B 建立連接。和普通的 HTTP 請求一樣,需要攜帶 IP + 端口號,如果有身份驗證的時候還會帶上授權信息,代理服務器 B 會使用授權信息進行驗證。然后代理服務器會去連接遠程主機,連接成功后返回 200。

Wireshark 抓到的包有這樣兩條信息,就是在創建代理:

No. Time Source Destionation Protocol Length Info
396 4.798832 172.17.32.211 172.17.32.19 HTTP 284 CONNECT nex.163.com:443 HTTP/1.1
401 4.816127 172.17.32.19 172.17.32.211 HTTP 105 HTTP/1.0 200 Connection established

請求報文:

CONNECT nex.163.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 5 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: nex.163.com

響應報文:

HTTP/1.0 200 Connection established

HTTP CONNECT 是在 HTTP1.1 新增的命令,用于支撐 https 加密。

因為我采用代理的方式抓包才有這一個步驟。如果是直接抓 PC 機上瀏覽器發出的 HTTPS 包,不會有這個過程。

然后我們思考一下,為什么代理服務器需要這些信息,要連接的主機名和端口號?

這是因為后面進行 SSL 加密 HTTP協議,因為代理服務器拿不到加密密鑰,是無法獲取到 HTTP 首部的,進而無法這個請求是要發到哪個主機的。所以,這里先使用 CONNECT 方法,把主機名和對應的端口號通知代理服務器。

這個也被稱為 HTTPS SSL 隧道協議。建立這個 SSL 隧道后,這個特殊代理就會對數據進行盲轉發。

2.3. TLS/SSL 握手

2.3.1. TLS/SSL 協議內容

SSL 整個協議實際上分兩層,SSL 記錄協議和其他子協議(SSL握手協議,SSL改變密碼協議,SSL警告協議):

SSL 協議

這兩層協議的關系,其實就是數據封裝的關系,SSL 握手封裝協議封裝其他上層協議。

封裝握手協議:

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 153
        Handshake Protocol: Client Hello

封裝應用數據協議,比如 HTTP:

Secure Sockets Layer
    TLSv1.2 Record Layer: Application Data Protocol: http
        Content Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 1072
        Encrypted Application Data: 6d9b3c9089271630c33506fe28cd6a61fed1f4bd2808f537...

封裝交換密碼協議:

Secure Sockets Layer
    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
        Content Type: Change Cipher Spec (20)
        Version: TLS 1.2 (0x0303)
        Length: 1
        Change Cipher Spec Message

封裝警報協議:

Secure Sockets Layer
    TLSv1.2 Record Layer: Encrypted Alert
        Content Type: Alert (21)
        Version: TLS 1.2 (0x0303)
        Length: 48
        Alert Message: Encrypted Alert

所以 SSL 記錄協議其實就是一個其他協議的載體,只是提供了一個封裝的功能。它的格式為:

| 內容類型 | 主要版本 | 次要版本 | 長度 |
|          明文/加密/壓縮 數據包       |
|          MAC(0, 16, 20)            |

MAC 就是消息驗證碼,用來驗證數據的完整性,保證中途沒有篡改。這個消息驗證碼比數字簽名弱一些,使用的是對稱密鑰加密摘要。數字簽名使用的是非對稱密鑰加密,有區分公鑰私鑰。

記錄協議的主要目的有這幾個,為其他 SSL 子協議提供了以下服務:

  • 分組、組合。如果有幾個協議的內容,是可以不用等客戶端確認后再發送的,這里會進行組合合并,然后在同一個 TCP 包中發送。接收方接收到組合的 SSL 記錄協議報文,也會根據協議來進行分組。
  • 壓縮、解壓縮。可選項,如果需要壓縮的話。

  • 消息認證(MAC)。注意,這里不提供數字簽名。消息認證用的是用對稱密鑰,解密算法效率比公鑰算法快,安全性弱一些。如果已經明確了,加密用的對稱密鑰只有合法的客戶端和服務端獲得,那么這個的安全效果和數字簽名一樣。

  • 加密傳輸 。比如已經協商好加密密鑰和算法了,直接對應用層協議加密傳輸:

    Encrypted Application Data: 6d9b3c9089271630c33506fe28cd6a61fed1f4bd2808f537...
    

TCP 三次握手結束并且和代理服務器成功連接后,建聯成功,客戶端 A 就開始發起 SSL 連接,首先會進入 SSL 握手階段。

SSL 握手階段的主要目的有這么幾個:

  • 協商加密算法。為了能夠提供效率,使用對稱密鑰。對稱加密使用的是位運算,速度快,甚至可以硬件加速。非對稱加密比如 RSA,使用了大數乘法等,整體會比較慢。對稱加密只要密鑰沒有泄漏,那也是非常安全的。這也是后面 SSL 握手協議要確保的。
  • 協商加密密鑰 。用來對后面的 HTTP 協議等應用協議內容進行加密。這個密鑰又稱為主密鑰,為加密算法的密鑰。
  • 驗證身份 。通常情況下,只要驗證服務端身份。特殊情況下,比如一些安全級別高的應用場景,還要驗證客戶端身份。服務端會返回證書鏈,有根 CA 證書在里頭。通過證書的鏈式擔保,可以確認服務端是否是可信任的。同時,在握手期間,公鑰傳輸成功后,還會對某些信息進行數字簽名,確保數據沒有被篡改且身份無誤。

SSL 握手的流程并不是一成不變的,根據實際的應用場景來。主要有三種:

  • 只驗證服務端。這個用三個階段就完成握手,我們這次的請求也是這樣。一般的網絡請求也僅僅到這個程度。
  • 驗證服務端和客戶端。在安全性要求較高的場景,服務端也要驗證客戶端的身份。方式也是發證書證明自己。
  • 恢復原有會話 。這個屬于HTTPS 優化的范疇。使用 Session Ticket 或者 Session ID 機制恢復之前已經完成握手的會話。這個是可以允許在不同的 TCP 上進行的。因為握手的加密數據已經保存,直接恢復就可以開始傳遞了。Session Ticket 由客戶端保存加密信息,Session ID 的方式由服務端保存加密信息。不過 Session Ticket 在 Android 客戶端還沒有得到廣泛的支持,和具體機型和內置的 OpenSSL 的版本有關。

SSL 握手的完整的交互過程如下,這里是驗證服務端又驗證了客戶端的情況:

SSL 握手

我們的請求只驗證服務端,所以 7,8,9 是不存在的。

現在具體分析每一個階段的內容。

2.3.2. 階段一

Client Hello

作為 SSL 握手的第一個握手包,我們詳細分析和理解一下包的內容。

下面是 Wireshark 解析好的這個 SSL 協議的數據包:

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 153
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 149
            Version: TLS 1.2 (0x0303)
            Random
                GMT Unix Time: Dec  1, 2050 02:37:13.000000000 ?й???????
                Random Bytes: 4e12e967b6169c4d67caf0575079f34b277d12318385f5a9...
            Session ID Length: 0
            Cipher Suites Length: 40
            Cipher Suites (20 suites)
            Compression Methods Length: 1
            Compression Methods (1 method)
            Extensions Length: 68
            Extension: server_name
            Extension: Extended Master Secret
            Extension: signature_algorithms
            Extension: ec_point_formats
            Extension: elliptic_curves

這個包如何解讀,按照之前對 SSL 協議的分析,其實分成兩個部分:

  • SSL 握手協議。
  • SSL 記錄協議。

因為是握手過程,密鑰還沒協商,這里還是使用明文傳輸,記錄協議的數據載體就是明文的 SSL 握手協議。

SSL 握手協議的格式為:

+-------+--------+----------------------
| 類型  | 長度    | 內容                   
+-------+--------+----------------------

我們可以從握手協議的數據包中得到這些信息:

  • 版本,TLS 1.2 也是 SSLv3.2。這是 SSL 客戶端能夠支持的 SSL 最高版本,主版本號 3,此版本號 2。TLS 目前的版本如下:

    Major Version Minor Version Version Type
    3 0 SSLv3
    3 1 TLS 1.0
    3 2 TLS 1.1
    3 3 TLS 1.2

    最后使用什么樣的版本,得由服務端決定。如果服務端不支持的話,客戶端得降版本。

  • 隨機數 ,生成一個32字節隨機數。最后加密數據用的主密鑰,需要客戶端和服務端一起協商出來。后面服務端的 Server Hello 階段也會生成一個隨機數。一同用來計算出主密鑰。

    Random Bytes: 4e12e967b6169c4d67caf0575079f34b277d12318385f5a9...
    
  • 會話ID ,這里為 0。這個 Session ID 是可以重用的,具體看服務端資源和支持情況。如果要復用 Session ID, SSL 服務端需要維護連接的狀態和上次握手成功留下的加密信息。因為這是這是第一次訪問該網址,會話 ID 尚未創建,客戶端沒記錄,這里為 0。如果客戶端保存了 Session ID 的信息,下次發起 SSL 請求的時候會帶上。

  • 加密套件 ,客戶端可以支持的密碼套件列表。這些套件會根據優先級排序。每一個套件代表一個密鑰規格。以 “TLS” 開頭,接著是密鑰交換算法,然后用 “WITH” 連接加密算法和認證算法。一個加密套件有這么幾個內容:密鑰交換算法、加密算法(會帶有支持的最高密鑰位數)、認證算法還有加密方式

    密鑰交換算法用在 SSL 握手階段的交換協商好的對稱密鑰的階段,為非對稱加密,比如:

    • EC Deffie-Hellman 密鑰交換算法。這里會被縮寫為 ECDHE,也稱為 DH 加密。
    • RSA 密鑰加密算法。

    加密算法,是最后要用來加密 HTTP 數據的,為對稱加密算法,比如:

    • DES
    • 3DES
    • AES

    摘要算法,也是對數據進行摘要。后面可以用來做數據的校驗,保證數據的一致性,讓中途被篡改的包失效,比如:

    • MD5
    • SHA1
    • SHA256

    所以這里一共應用了三種密鑰技術,非對稱密鑰,對稱密鑰和摘要算法。用一句話總結:用非對稱加密算法來傳遞對稱加密算法的密鑰,同時用摘要算法保證數據的完整性。

    這一次請求,客戶端提供了 20 種密碼套件供服務端選擇,最終使用什么密碼套件是服務端決定的。要什么密碼套件會在 Server Hello 中進行反饋。

    Cipher Suites (20 suites)
        Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
        Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
        Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
        Cipher Suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f)
        Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
        Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
        Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
        Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
        Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039)
        Cipher Suite: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA (0xc007)
        Cipher Suite: TLS_ECDHE_RSA_WITH_RC4_128_SHA (0xc011)
        Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
        Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
        Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
        Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
        Cipher Suite: TLS_RSA_WITH_RC4_128_SHA (0x0005)
        Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)
    
  • 壓縮算法 ,這里為 0,說明不支持壓縮算法
  • 擴展字段 ,一些擴展信息,比如 SNI 的支持,ALPN 的信息等等。

密碼套件隨著密碼學的發展而發展,而且根據現實應用中,可能會有某些密碼被破解,從而導致密碼套件可能會導致安全問題,所以一般都會使用當前最新最安全的密碼套件。

在 Android 系統中,一般情況下,使用 SSLSocket進行連接的時候,會帶上系統默認的支持的密碼套件。但是這個有個缺點,比如某些密碼套件的加密算法被破解或者出現安全漏洞,而且要跟著系統升級反應緩慢。OkHttp 在進行 SSL 握手的時候,會使用 ConnectionSpec 類中帶上提供了一系列最新的密碼套件。可以從注釋上看,這些密碼套件在 Chrome 51 和 Android 7.0 以上得到了完全支持。

OkHttp 密碼套件

然后,再把這些密碼套件和 Android 系統支持的密碼套件取交集,提交給服務端。這樣,萬一哪個密碼套件有問題,OkHttp 官方會下降支持。網絡庫 OkHttp 庫會隨著版本的迭代,不斷地去提供比較新的密碼套件,并且放棄那些不安全的密碼套件。接入應用即時更新 OkHttp,就不用等待緩慢的系統更新了。

如果提供的所有密碼套件服務端都不支持,OkHttp 有回退機制,退而求其次,選比較舊的套件。

2.3.3. 階段二

Server Hello

服務端收到了客戶端的 Hello,通過客戶端的配置信息,結合服務端的自身情況,給出了最終的配置信息。

Wireshark 解析后的內容如下:

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 93
        Handshake Protocol: Server Hello
            Handshake Type: Server Hello (2)
            Length: 89
            Version: TLS 1.2 (0x0303)
            Random
                GMT Unix Time: Jul  4, 2022 12:28:57.000000000 ?й???????
                Random Bytes: 6e15dfda5067399e9cd552ba30fb961914c5ce0e3f61d8f8...
            Session ID Length: 32
            Session ID: 7a92546002c514f9a0b11ef585935c7cc5182d9db3ef0db3...
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
            Compression Method: null (0)
            Extensions Length: 17
            Extension: server_name
            Extension: renegotiation_info
            Extension: ec_point_formats

具體內容如下:

  • 版本,指定這次 SSL 使用 TLSv1.2 版本。

  • 隨機數 ,上面的 Client Hello 過程也生產了一個 32 位隨機數,這兩個隨機數將參與主密鑰(master key)的創建。

    Random Bytes: 6e15dfda5067399e9cd552ba30fb961914c5ce0e3f61d8f8...
    
  • 會話ID ,這里不為 0。說明服務端允許客戶端再以后的 SSL 連接中復用這次會話。在使用 HTTPS 的 Session Ticket 可以用到。會話 ID 由服務端維持,采用恢復會話的方式創建 SSL 連接。

    Session ID: 7a92546002c514f9a0b11ef585935c7cc5182d9db3ef0db3...
    
  • 加密套件TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 。這個是從客戶端 Client Hello 上傳的 20 個加密套件中選中的,根據密碼套件的格式,上面的信息有,

    交換加密算法為ECDHE ,就是EC Diffie-Hellman ,RSA 表示后面 Server Key Exchange 階段的攜帶 DH 加密算法的公鑰的包的數字簽名的加密算法是 RSA。

    加密算法為 AES ,最高密鑰支持 128 位,使用 CBC 分組。

    認證算法 SHA 。所謂 CBC 就是 AES 的機密模式,為分組加密。ECDHE_RSA,表示交換加密算法為 ,RSA 是后面的 獲取 ECDHE 的參數的包進行的數字簽名用的算法。

  • 壓縮方法 ,這里為 0,表示不使用壓縮算法。

Certificate

上面的 Server Hello 已經制定了接下來的非對稱加密算法

服務端下發證書,客戶端驗證服務端的身份,并且取出證書攜帶的公鑰,這個公鑰是交換加密算法的公鑰。也就是在 Server Hello 階段指定的 ECDHE (EC Diffie-Hellman)算法,也是通常說的 DH 加密。

這個 Certificate 消息下發了從攜帶自己公鑰的數字證書和 CA 證書的證書鏈,在 Certificates 字段中:

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Certificate
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 2403
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 2399
            Certificates Length: 2396
            Certificates (2396 bytes)
                Certificate Length: 1283
                Certificate: 308204ff308203e7a0030201020210248b4dd17f3ef11a77... 
                Certificate Length: 1107
                Certificate: 3082044f30820337a0030201020203023a6f300d06092a86... 

CA 是 PKI 體系的重要組成部分,稱為認證機構。

那什么是 CA 證書?就是用來 CA 中心發布的,認證該服務單證書的合法性,可以確保該證書來源可靠而不是被中間人替換了。但是 CA 證書也可能被中間人攔截造假?那就再用一個證書來認證它。看起來好像沒完沒了。實際上到最后有一個根 CA 證書,這個證書存儲再瀏覽器或者操作系統中,是系統直接信任的。

服務端證書需要 CA 證書做認證。使用的還是數字簽名方式,從數據中摘要一段信息,用 CA 證書的加密。然后驗證的時候時候,用 CA 證書的公鑰解密,用同樣的摘要算法摘要數據部分和解密好的信息進行比較。

證書的簽名和驗簽

客戶端在驗證服務端證書的有效性有這樣的一個過程。首先會找到該證書的認證證書,也就是中級 CA 證書。然后找中級 CA 證書的認證證書,可以是另一個中級 CA 證書,也可能是根 CA 證書。這樣直到根 CA 證書。

接著從根 CA 證書開始往下去驗證數字簽名。比如有這樣的證書鏈:根 CA 證書-> 中級 CA 證書 -> 服務端證書。用 CA 證書的公鑰去驗證中級證書的數字簽名,再用中級證書的公鑰去驗證服務器證書的數字簽名。任何一個環節驗證失敗,就可以認為證書不合法。

這就是整個證書鏈的認證過程:

證書驗證過程

查看抓到的包的數據,發現只有兩個證書。為服務端證書和中級 CA 證書。根 CA 證書呢?順藤摸瓜找到它。

首先看服務端證書。它內容如下:

Certificate: 308204ff308203e7a0030201020210248b4dd17f3ef11a77... 
    signedCertificate
        version: v3 (2)
        serialNumber: 0x248b4dd17f3ef11a7733fead4cd68c21
        signature (sha256WithRSAEncryption)
        issuer: rdnSequence (0)
            rdnSequence: 3 items
                RDNSequence item: 1 item (id-at-countryName=US)
                RDNSequence item: 1 item (id-at-organizationName=GeoTrust Inc.)
                RDNSequence item: 1 item (id-at-commonName=GeoTrust SSL CA - G3)
        validity
            notBefore: utcTime (0)
                utcTime: 15-10-14 00:00:00 (UTC)
            notAfter: utcTime (0)
                utcTime: 17-12-30 23:59:59 (UTC)
        subject: rdnSequence (0)
            rdnSequence: 6 items
                RDNSequence item: 1 item (id-at-countryName=CN)
                RDNSequence item: 1 item (id-at-stateOrProvinceName=Zhejiang)
                RDNSequence item: 1 item (id-at-localityName=Hangzhou)
                RDNSequence item: 1 item (id-at-organizationName=NetEase (Hangzhou) Network Co., Ltd)
                RDNSequence item: 1 item (id-at-organizationalUnitName=MAIL Dept.)
                RDNSequence item: 1 item (id-at-commonName=*.163.com)
        subjectPublicKeyInfo
            algorithm (rsaEncryption)
            Padding: 0
            subjectPublicKey: 3082010a0282010100ce9da6fb3a940ae04a939b85a1a961...
        extensions: 8 items
            Extension (id-ce-subjectAltName)
            Extension (id-ce-basicConstraints)
            Extension (id-ce-keyUsage)
            Extension (id-ce-cRLDistributionPoints)
            Extension (id-ce-certificatePolicies)
            Extension (id-ce-extKeyUsage)
            Extension (id-ce-authorityKeyIdentifier)
            Extension (id-pe-authorityInfoAccessSyntax)
    algorithmIdentifier (sha256WithRSAEncryption)
    Padding: 0
    encrypted: ad75f99ff40e582bc9a17c01c98edf02696aa4f821dfe870...

從這個證書中我們可以窺見這些信息:

首先是 signedCertificate 字段的內容,即數字證書的數據:

  • 版本,version,v3。對應的就是 X.509 V3 標準。

  • 序列號 ,serialNumber,0x248...,證書頒發者唯一序列號。

  • 簽名算法ID ,Signature Algorithm。這里指的是使用 SHA-256 進行摘要,RSA 進行加密的簽名算法。

  • 證書頒發者 ,issuer,就是頒發該證書的 CA 的信息。里面攜帶后該 CA 的唯一名稱(DN,Distinguished Name),比如國家為 US(美國),組織機構為 GeoTrust Inc.,名稱為 GeoTrust SSL CA - G3。后面我們需要從證書鏈找到該 CA 證書,去認證當前證書。

  • 有效期 ,validity,證書的起始時間和終止時間。可以得出該證書到 17 年 12 月 30 日后過期。

  • 對象名稱 ,subject,里面就是該證書的名稱等主要信息了。比如國家為 CN(中國),組織為 NetEase (Hangzhou) Network Co., Ltd。名稱為 *.163.com ,也是該證書適用的域名。

  • 對象公鑰信息 ,subjectPublicKeyInfo。因為這是服務端證書,這個公鑰后面將用于主密鑰的交換過程,從中可以了解到這個公鑰采用 RSA 加密,公鑰內容為 3082010a0282010100ce9da6fb3a940ae04a939b85a1a961...

  • 擴展部分 ,一些擴展信息。比如對象的別名。這個如果是 CDN 的服務器證書,那么別名將會非常多。是所有使用該 CDN 的網站的域名。比如之前抓到的 CDNetworks 的 CDN 證書,它的擴展字段 subjectAltName 有這些信息:

    Certificate: 308220a630821f8ea00302010202100b8a2d407d6121375c... 
        signedCertificate
            version: v3 (2)
            serialNumber: 0x0b8a2d407d6121375cba2fac96354a41
            signature (sha256WithRSAEncryption)
            issuer: rdnSequence (0)
            validity
            subject: rdnSequence (0)
                rdnSequence: 5 items
                    RDNSequence item: 1 item (id-at-countryName=US)
                    RDNSequence item: 1 item (id-at-stateOrProvinceName=California)
                    RDNSequence item: 1 item (id-at-localityName=Campbell)
                    RDNSequence item: 1 item (id-at-organizationName=CDNetworks Inc.)
                    RDNSequence item: 1 item (id-at-commonName=support13.cdnetworks.net)
            subjectPublicKeyInfo
            extensions: 10 items
                Extension (id-ce-authorityKeyIdentifier)
                Extension (id-ce-subjectKeyIdentifier)
                Extension (id-ce-subjectAltName)
                    Extension Id: 2.5.29.17 (id-ce-subjectAltName)
                    GeneralNames: 314 items
                        GeneralName: dNSName (2)
                            dNSName: support13.cdnetworks.net
                        GeneralName: dNSName (2)
                            dNSName: china.ray-ban.com
                        GeneralName: dNSName (2)
                            dNSName: static1.read.ru
                        GeneralName: dNSName (2)
                            dNSName: ps4.cache.square-enix.co.jp
                        ...
                Extension (id-ce-keyUsage)
                Extension (id-ce-extKeyUsage)
                Extension (id-ce-cRLDistributionPoints)
                Extension (id-ce-certificatePolicies)
                Extension (id-pe-authorityInfoAccessSyntax)
                Extension (id-ce-basicConstraints)
                Extension (iso.3.6.1.4.1.11129.2.4.2)
        algorithmIdentifier (sha256WithRSAEncryption)
        Padding: 0
        encrypted: 68728010fd9e4b7e3bdbe5e47ec680330b6851e1ea4dc737...
    
    

    可以了解到該 CDN 為 314 個域名提供服務。

然后是證書頒發機構的簽名信息:

  • 簽名算法,algorithmIdentifier。這里得出使用的還是 SHA-256 摘要加 RSA 加密的簽名算法。這個就是認證該證書的 CA 證書使用的簽名算法。
  • 簽名信息 ,encrypted,這個信息的內容,CA 證書對 SHA-256 對上面的數據部分進行摘要后,使用 RSA 的私鑰加密獲得。后面會用在該證書的認證過程,取出 CA 證書的公鑰,解密簽名信息,用同樣的算法獲取數據摘要,對比一下是否相同。

從上面的 issuer 可以了解到,認證該服務器證書的 CA 證書為 GeoTrust SSL CA - G3 ,我們從 Certificates 找到對應的中級證書的內容如下(中級證書可以有好幾級,我們這兒只有一級):

Certificate: 3082044f30820337a0030201020203023a6f300d06092a86... 
    signedCertificate
        version: v3 (2)
        serialNumber: 146031
        signature (sha256WithRSAEncryption)
        issuer: rdnSequence (0)
            rdnSequence: 3 items
                RDNSequence item: 1 item (id-at-countryName=US)
                RDNSequence item: 1 item (id-at-organizationName=GeoTrust Inc.)
                RDNSequence item: 1 item (id-at-commonName=GeoTrust Global CA)
        validity
            notBefore: utcTime (0)
                utcTime: 13-11-05 21:36:50 (UTC)
            notAfter: utcTime (0)
                utcTime: 22-05-20 21:36:50 (UTC)
        subject: rdnSequence (0)
            rdnSequence: 3 items
                RDNSequence item: 1 item (id-at-countryName=US)
                RDNSequence item: 1 item (id-at-organizationName=GeoTrust Inc.)
                RDNSequence item: 1 item (id-at-commonName=GeoTrust SSL CA - G3)
        subjectPublicKeyInfo
            algorithm (rsaEncryption)
            Padding: 0
            subjectPublicKey: 3082010a0282010100e3be7e0a86a3cf6b6d3d2ba197ad49...
        extensions: 8 items
            Extension (id-ce-authorityKeyIdentifier)
            Extension (id-ce-subjectKeyIdentifier)
            Extension (id-ce-basicConstraints)
            Extension (id-ce-keyUsage)
            Extension (id-ce-cRLDistributionPoints)
            Extension (id-pe-authorityInfoAccessSyntax)
            Extension (id-ce-certificatePolicies)
            Extension (id-ce-subjectAltName)
    algorithmIdentifier (sha256WithRSAEncryption)
    Padding: 0
    encrypted: a0d4f72cfb740b7f64f1cd436a9f62531c027c9890a2ee4f...

可以得到中級證書名為 GeoTrust SSL CA - G3 ,證書組織為 GeoTrust Inc.

認證該 CA 證書的證書呢?還是看 issue 字段,認證證書名為 GeoTrust Global CA ,組織同樣是 GeoTrust Inc.

其實這個就是根 CA 證書。在這個請求中沒有找到,但在瀏覽器或者操作系統可以找到。一般的瀏覽器和系統都會內置該 CA 證書。所以根證書是受瀏覽器或者操作系統信任的,無需其他證書做擔保。

如果想要自己的系統再信任某些非通用的權威機構的根 CA 證書,那么就去安裝它。

比如我的 Windows 系統就安裝了 GeoTrust Global CA 證書:

Windows 的證書管理

像我們平時使用 Charles 抓 HTTPS 就是這個原理,把 Charles 的 CA 證書安裝在手機中,成為受信任的根 CA 證書。

基本原理就是,Charles 代理作為 SSL 隧道,并沒有透明傳輸,而是作為一個中間人,攔截了 SSL 握手信息,修改里面的 CA 證書。仿冒手機端和真實服務端建立連接獲取主密鑰,然后又仿冒服務端和手機客戶端建立 SSL 連接,修改服務端證書的 CA 和數字簽名,這樣 Charles 就可以解析到加密的 HTTP 內容了。

修改后的服務端證書如下,可以看到 issuer 被替換成了 Charles 的證書。

Certificate: 30821ff230821edaa0030201020206015e7406ab1c300d06... 
    signedCertificate
        version: v3 (2)
        serialNumber: 1505185147676
        signature (sha256WithRSAEncryption)
        issuer: rdnSequence (0)
            rdnSequence: 6 items
                RDNSequence item: 1 item (id-at-commonName=Charles Proxy Custom Root Certificate)
                RDNSequence item: 1 item (id-at-organizationalUnitName=http://charlesproxy.com/ssl)
                RDNSequence item: 1 item (id-at-organizationName=XK72 Ltd)
                RDNSequence item: 1 item (id-at-localityName=Auckland)
                RDNSequence item: 1 item (id-at-stateOrProvinceName=Auckland)
                RDNSequence item: 1 item (id-at-countryName=NZ)
        validity
        subject: rdnSequence (0)
            rdnSequence: 5 items
                RDNSequence item: 1 item (id-at-countryName=US)
                RDNSequence item: 1 item (id-at-stateOrProvinceName=California)
                RDNSequence item: 1 item (id-at-localityName=Campbell)
                RDNSequence item: 1 item (id-at-organizationName=CDNetworks Inc.)
                RDNSequence item: 1 item (id-at-commonName=support13.cdnetworks.net)
        subjectPublicKeyInfo
        extensions: 7 items
    algorithmIdentifier (sha256WithRSAEncryption)
    Padding: 0
    encrypted: da491fc58682c7b85751db9def0b366d58cf09755ab8ef7d...

到這個階段,我們有了一個小想法,是不是可以自己搞個根 CA ,然后推廣到各個設備上使用?

理論上可以。但是推廣成本太高,市場上被大多數系統認可的就這幾家機構:

  • Symantec(VeriSign/GeoTrust)
  • Comodo
  • GoDaddy

像 BAT 這樣的大廠也需要買它們的證書。像我這次抓的是網易的包,使用的也是 GeoTrust 。不過也有例外,我們的 12306 比較任性,就沒有購買這些機構的證書,所以上這個網站在 Chrome 等瀏覽器經常會彈出不受信任等等。

Server Key Exchange

密鑰交換階段,這個步驟是可選步驟,對 Certificate 階段的補充,只有在這幾個場景存在:

  • 協商采用了 RSA 加密,但是服務端證書沒有提供 RSA 公鑰。
  • 協商采用了 DH(EC Diffie-Hellman) 加密,但是服務端證書沒有提供 DH 參數。
  • 協商采用 fortezza_kea 加密,但是服務端證書沒有提供參數。

我們滿足了哪一個場景?

可以知道我們前面協商了使用 EC Diffie-Hellman 算法,而且沒帶參數,所以這個包就是服務端帶過來的用來協商 DH 密鑰參數的。

TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 333
    Handshake Protocol: Server Key Exchange
        Handshake Type: Server Key Exchange (12)
        Length: 329
        EC Diffie-Hellman Server Params
            Curve Type: named_curve (0x03)
            Named Curve: secp256r1 (0x0017)
            Pubkey Length: 65
            Pubkey: 04a10ad7a23135095205caf7ca8e4c838728e877dbcb23c3...
            Signature Hash Algorithm: 0x0601
                Signature Hash Algorithm Hash: SHA512 (6)
                Signature Hash Algorithm Signature: RSA (1)
            Signature Length: 256
            Signature: 8c7c51f60574144e9e1385a534e12f85911e8dc7cd40dc04...

這個包把 DH 算法需要的公鑰給傳遞過來了,即 Pubkey: 04a10ad7a23135095205caf7ca8e4c838728e877dbcb23c3...

同樣這個包也攜帶了數字簽名 Signature: 8c7c51f60574144e9e1385a534e12f85911e8dc7cd40dc04... ,用服務端證書帶過來的公鑰驗證一下完整性和來源。

Server Hello Done

通知客戶端,版本和加密套件協商結束。

TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 4
    Handshake Protocol: Server Hello Done
        Handshake Type: Server Hello Done (14)
        Length: 0

這個 Server Hello Done,就像 TCP 協議的 ACK 確認包一樣,這里服務端也給了個確認的信息,通知客戶端已經做好進入下一個階段的準備。

通過 Wireshark 抓包發現了一個現象,就是 Server Key Exchange 和 Server Hello Done 被放到了同一個 SSL 記錄協議中,這是因為 SSL 記錄協議具有組合功能。客戶端收到這樣的包后,會處理成兩個單獨的協議包,這又是 SSL 記錄協議的分組功能。

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done

這樣做的好處,可以減少發 TCP 包的次數,減少 SSL 握手的時間。

2.3.4. 階段三

如果在一些安全級別高的場景,服務端也會要求客戶端上報證書,會有 Certificate Request 的 SSL 握手報文。這樣的情況下,接下來會有完整的客戶端證書上報服務端的流程。整個流程和階段二類似。

2.3.5. 階段四

因為我們這里只需要驗證服務端的證書,所以直接進入階段四,開始最后的握手。這個階段的主要目的,就是生產加密密鑰,并進行安全傳輸。

Client Key Exchange

這里,客戶端不直接生成加密密鑰,而是通過之前客戶端和服務端生成的隨機數又再生成一個隨機數,使用前面協商好的用 EC Diffie-Hellman 算法進行加密傳輸給服務端。這個值又被稱為 “premaster secret“。

TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 70
    Handshake Protocol: Client Key Exchange
        Handshake Type: Client Key Exchange (16)
        Length: 66
        EC Diffie-Hellman Client Params
            Pubkey Length: 65
            Pubkey: 0433cfbd121d0fa5299819604a15237fc9359845a2a9dffe...

服務端收到這個報文后,會使用自己的私鑰解開這個隨機數。

在這個階段過后,服務端和客戶端都有三個隨機數:客戶端隨機數、服務端隨機數和預備主密鑰。

在服務端收到了 Client Key Exchange 消息后,兩端都按照相應的算法生成了主密鑰,加密密鑰交換完成。

交換完了,因為主密鑰是兩個端按照約定好的算法產生的,如何保證這個主密鑰是正確的?

這時候會進入下一個階段。客戶端和服務端會對握手信息使用 SHA 做個摘要,用 AES 加密算法和主密鑰加密,傳遞給對方驗證。這種方式也稱為消息認證。就是下面的過程:

Change Cipher Spec(Client)

客戶端通知服務端,后續的報文將會被加密。

Encrypted Handshake Message(Client)

這里就是客戶端的 Client Finished 消息。

也是整個 SSL 過程中,發送給服務端的第一個加密消息。

服務端接收后,服務端用同樣的方式計算出已交互的握手消息的摘要,與用主密鑰解密后的消息進行對比,一致的話,說明兩端生成的主密鑰一致,完成了密鑰交換。

Change Cipher Spec(Server)

服務端通知客戶端,后續的報文將會被加密。

Encrypted Handshake Message(Server)

這里就是服務端的 Server Finish 消息。

和上面的客戶端的 Encrypted Handshake Message 一樣,是服務端發出的第一條加密信息。

客戶端按照協商好的主密鑰解密并驗證正確后,SSL 握手階段完成。

2.3.6. 小結

整個 SSL 握手主要是要完成這幾個目標:

  • 對服務端身份的驗證,或者客戶端
  • 協商好對稱加密算法和對稱加密密鑰

對應 Java API 為 SSLSocket :

sslSocket.startHandshake();

這次請求的整個過程耗時大約為 380ms。可以看出,SSL 握手是很消耗請求時間的。所以對握手進行優化,比如使用 Session ID 或者 Session Ticket。這個類似于 HTTP 協議的 Session 和 Cookie 的使用。

2.4. 數據傳輸

經過了 SSL 握手后,服務端的身份認證成功,協商出了加密算法為 AES,密鑰為 xxxxx(客戶端和服務端拿三個隨機值用相同算法計算出來的,并沒有明文傳輸)。一切準備就緒。

SSL 握手成功,已經可以對接下來的數據加密了,接下來各種應用層協議都可以加密傳輸。

2.4.1. Application Data

應用數據傳輸消息。因為這里是 HTTPS,所以可以對 HTTP 應用協議數據加密然后傳輸了。

Secure Sockets Layer
    TLSv1.2 Record Layer: Application Data Protocol: http
        Content Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 1072
        Encrypted Application Data: 6d9b3c9089271630c33506fe28cd6a61fed1f4bd2808f537...

從這里,不知道密鑰是無法知道這里傳輸的是什么數據,連傳輸的是什么協議的內容都不知道。

所以之前創建 SSL 隧道,讓代理服務器盲傳 HTTPS 數據,就得通過 CONNECT 方法告訴代理服務器要連哪臺主機,哪個端口號,要不然代理服務器也是一臉懵逼。

所以 SSL 協議是很獨立的,這里是對 HTTP 進行了加密,也可以對其他協議進行加密。它就像是 TCP 和應用層協議的中間層,為上層協議提供了加密的數據傳輸。

2.4.2. Encryted Alert

SSL 警告消息,因為是加密的內容,所以單從 Wireshark 看不出警報的內容。

Secure Sockets Layer
    TLSv1.2 Record Layer: Encrypted Alert
        Content Type: Alert (21)
        Version: TLS 1.2 (0x0303)
        Length: 48
        Alert Message: Encrypted Alert

但因為警報消息經常只是客戶端用來提示服務端 SSL 傳輸結束,對照抓包到的內容確實如此。所以這里只是 SSL 傳輸結束的一個信號。

發出了 Encryted Alert 后客戶端數據傳輸完畢,準備進入四次揮手斷開 TCP 連接。

2.5. 四次揮手

客戶端發送完 HTTP 的數據,也正確地獲取到服務端的響應。完成了 HTTPS 的請求工作后,接下來要關閉 TCP 連接。關閉 TCP 連接一共分成四步,也可以成為四次揮手。對應包如下:

No. Time Source Destionation Protocol Length Info
688 7.607349 172.17.32.211 172.17.32.19 TCP 66 35973 → 8888 [FIN, ACK] Seq=1657 Ack=3213 Win=96512 Len=0 TSval=15986483 TSecr=59355534
689 7.607363 172.17.32.19 172.17.32.211 TCP 66 8888 → 35973 [ACK] Seq=3213 Ack=1658 Win=65024 Len=0 TSval=59355763 TSecr=15986483
690 7.620494 172.17.32.19 172.17.32.211 TCP 66 8888 → 35973 [FIN, ACK] Seq=3213 Ack=1658 Win=65024 Len=0 TSval=59355764 TSecr=15986483
697 7.661600 172.17.32.211 172.17.32.19 TCP 66 35973 → 8888 [ACK] Seq=1658 Ack=3214 Win=96512 Len=0 TSval=15986494 TSecr=59355764

可以看到,這四個包都只有 66 個字節。因為不帶數據,是純粹的 TCP 首部。

2.5.1. Round 1

客戶端 A 發出 FIN + ACK 包,通知服務端數據傳輸結束,可以關閉連接了。同樣這是一個 ACK 確認包,指出希望的下一個包的序號為 3213。

這個階段后,客戶端進入 FIN_WAIT_1 狀態,不再發送數據給服務端,但是如果服務端還在傳數據過來,客戶端的 ACK 確認報文還會有。

服務端進入 CLOSE_WAIT 狀態。這個狀態再發出 ACK 確認包后解除。

2.5.2. Round 2

接收端 B 收到第一次揮手的包后,會先給一個 ACK 確認包,為第二次揮手。

這里有個疑問,既然收到了 A 的結束信息,為什么不馬上結束呢?因為 A 完成數據傳輸,但是 B 可能還有數據沒有傳完,所以比三次握手會多一個步驟。

收到客戶端的 FIN 包后,已經知道客戶端結束數據傳輸了,所以服務端后面數據傳輸結束,就可以直接通知客戶端可以結束了。

客戶端收到 ACK 確認包后,進入 FIN_WAIT_2 狀態。

2.5.3. Round 3

如果服務端數據還沒有傳完,會繼續傳給客戶端。

等服務端的數據完全傳完后,會再發一個 FIN + ACK 包,通知客戶端數據傳完了。

這個階段后,服務端進入 LAST_ACK 狀態,即等待客戶端最后一個 ACK 報文。

2.5.4. Round 4

發送端 A 收到 B 發出的 FIN + ACK 后,進入 TIME_WAIT 狀態。服務端收到 FIN 后進入 CLOSED 狀態關閉連接。

經過 2MSL 時間,沒有問題后會關閉連接。也進入 CLOSED 狀態。

為什么有個 TIME_WAIT

原因是有可能服務端一直沒有收到 FIN + ACK,有可能觸發超時重傳,又發了一個 FIN 給客戶端,客戶端要重新發送最后一個包。

2.5.5. 小結

TCP 四次揮手的時序圖如下:

四次揮手時序圖

3. 擴展

3.1. Session ID 和 Session Ticket

3.2. SNI(Server Name Indication)

3.3. ALPN(Application Layer Protocol Negotiation)

4. 資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容