
大家好,我是毛衣哥。上一期我們把 HTTP 扒光了,這一期來看看加了鎖的 HTTPS 又是怎么被一步步擰開的。準備好瓜子,七步拆完。上一篇我們把 HTTP 裸奔的慘狀全抖出來了。有人問:那 HTTPS 一加密,中間人是不是就徹底沒轍了?答案是:要看它卡在哪一步。如果你不理解 TLS 握手每一步的精確內(nèi)容,你就永遠不會知道中間人到底在哪個環(huán)節(jié)做了手腳。這篇可能會有點長,但我保證這是你看過最詳細的 TLS 握手拆解。TLS 的目標就兩個,記死了所有加密通信要解決的根本問題永遠只有兩個:第一:對方是它自稱的那個人嗎?(身份認證)第二:怎么商量一把只有我倆知道的鑰匙?(密鑰協(xié)商)第一個問題靠CA 證書。第二個問題靠非對稱加密。TLS 握手就是同時完成這兩件事的過程。七步定乾坤:完整 TLS 1.2 握手拆解我在紙上畫了無數(shù)遍。TLS 1.2 的完整握手,不算兩端內(nèi)部的密鑰派生,正好七個 TCP 包。第一步:ClientHello(客戶端發(fā)起通話)客戶端發(fā)一條明文消息給服務器。里面寫了什么?前 4 字節(jié)(時間戳):GID // 0x474944 就是 ASCII 的 "GID"開個玩笑。實際 TLS 的 ClientRandom 的前 4 字節(jié)是 Unix 時間戳:00 00 00 00 - 版本號(TLS 1.2 = 0x0303,這里只舉例)不對,我認真說:ClientHello 的精確結(jié)構(gòu):1 字節(jié):TLS 內(nèi)容類型(0x16 = Handshake) 2 字節(jié):協(xié)議版本(0x0301 = TLS 1.0,但實際內(nèi)容看下面) 2 字節(jié):長度(指整個 Handshake 消息的長度) Handshake 消息體: 1 字節(jié):握手類型(0x01 = ClientHello) 3 字節(jié):長度 2 字節(jié):ClientHello 版本(0x0303 = TLS 1.2) 32 字節(jié):ClientRandom(前 4 字節(jié)是 Unix 時間戳,后 28 字節(jié)純隨機) ……然后是一系列擴展:關(guān)鍵內(nèi)容拆解:① 密碼套件列表客戶端列出一堆它支持的密碼套件,每個套件 2 字節(jié)。你猜有多少個?通常是 15-30 個。Cipher Suites (16 suites): TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) ← 最強推薦 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) ← 也很強 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c) TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c) ← 沒有 Forward Secrecy ……注意:密碼套件名字里每個段都有含義:密鑰交換算法:ECDHE、RSA、DHE證書簽名算法:ECDSA、RSA對稱加密算法:AES_128_GCM、AES_256_GCM、CHACHA20_POLY1305HMAC 算法:SHA256、SHA384② 32 字節(jié)的 ClientRandom這 32 個字節(jié)很重要。它不是隨便生成的隨機數(shù)——它跟后面 ServerRandom、Pre-Master Secret 一起,經(jīng)過一個偽隨機函數(shù)(PRF)推導出最終的 Master Secret。整個過程輸入的三個參數(shù)里,只要有一個變了,最終密鑰就完全不同。③ SNI 擴展(這個最騷)Extension: server_name (type=0x0000, length=…) Server Name Indication extension Server Name list length: … Server Name Type: host_name (0x00) Server Name length: … Server Name: www.dumpany.cn看到了嗎?你訪問的域名在 ClientHello 里是明文傳輸?shù)?!即使使?HTTPS,中間人也能從 ClientHello 的 SNI 擴展里知道你正在訪問哪個網(wǎng)站。這就解釋了為什么有些國家的防火墻能封掉特定網(wǎng)站——它不需要解密 TLS,只需要看 ClientHello 里的 SNI。域名是明文的。這也是為什么 ECH(Encrypted Client Hello,加密的 ClientHello)在最近的協(xié)議版本中被大力推進——它要把這最后一塊明文的拼圖也給加密。④ ALPN 擴展客戶端說:“我支持這些應用層協(xié)議:http/1.1、h2(HTTP/2)”。服務器會挑一個雙方都支持的。這在抓包里可以看到協(xié)商結(jié)果。第二步:ServerHello(服務器回禮)服務器回復,也是明文:1 字節(jié):0x16(Handshake) 2 字節(jié):0x0303(TLS 1.2) 2 字節(jié):長度 Handshake 內(nèi)容: 1 字節(jié):0x02(ServerHello) 3 字節(jié):長度 2 字節(jié):服務器選定的 TLS 版本(一般也是 0x0303) 32 字節(jié):ServerRandom 1 字節(jié):Session ID 長度 可變:Session ID(如果有) 2 字節(jié):選定的密碼套件(比如 0xc02f) 1 字節(jié):選定的壓縮方法(0x00 = 不壓縮) 可變:擴展關(guān)鍵:ServerRandom 也是一個 32 字節(jié)的隨機數(shù)。它的作用是保證即使兩個客戶端在同一毫秒發(fā)了同樣的 Pre-Master Secret(幾乎不可能),最終的會話密鑰也完全不同。第三步:Certificate(服務器亮出身份證)這一步信息量極大,是整個 TLS 握手最值得細細品味的消息。服務器發(fā)來自己的證書鏈——注意不是只有一個證書,是多個。正常情況下的證書鏈:證書 1(葉子證書 / 服務器證書) Version: 3 (0x02) Serial Number: 04:79:fb:8a:... Signature Algorithm: sha256WithRSAEncryption Issuer: C=US, O=Let's Encrypt, CN=R3 ← 誰簽發(fā)了我?(中間 CA 名) Validity Not Before: Jul 20 01:00:00 2026 GMT Not After : Oct 18 01:00:00 2026 GMT Subject: CN=dumpany.cn ← 這個證書是發(fā)給誰的 Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) ... X509v3 extensions: X509v3 Subject Alternative Name: ← 這就是 SAN 列表 DNS:dumpany.cn, DNS:www.dumpany.cn ← 所有受保護的域名 CA:FALSE ← 這個證書不能做 CA ... 證書 2(中間 CA 證書) Version: 3 (0x02) Serial Number: ... Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1 ← 根證書簽發(fā)了我 Subject: C=US, O=Let's Encrypt, CN=R3 ← 我就是 Let's Encrypt 的中間 CA X509v3 extensions: CA:TRUE ← 這個證書是 CA(可以簽發(fā)其他證書) ... 證書 3(根證書——一般不發(fā)送,因為瀏覽器已經(jīng)有了)在這個消息里,中間人能讀到什么?明文!在 TLS 1.2 里,Certificate 消息是明文的。中間人可以看到完整的證書內(nèi)容——包括你用的哪個 CA、證書什么時候過期、你的公鑰是多少。這也意味著:在 TLS 1.2 里,中間人在握手階段就能判斷你訪問的是哪個網(wǎng)站——即使 SNI 被加密了(實際上沒有),從證書的 CN 或 SAN 也能讀到域名。這也是 TLS 1.3 要改善的地方之一——在 1.3 里,Certificate 消息是加密的。第四步:ServerKeyExchange(如果有的話)這一步只在使用了 DHE/ECDHE 密鑰交換時才出現(xiàn)。如果是 RSA 密鑰交換,沒有這一步。服務器發(fā)了什么:1 字節(jié):0x0c(ServerKeyExchange) 3 字節(jié):長度 內(nèi)容: 1 字節(jié):橢圓曲線類型(named_curve = 0x03) 2 字節(jié):曲線名稱(x25519 = 0x001d,或 secp256r1 = 0x0017) 1 字節(jié):公鑰長度 可變:服務器 DH 公鑰 2 字節(jié):簽名長度 可變:簽名(用自己的證書私鑰簽的)這一步是 ECDHE 的核心所在。服務器說:“這是我的 DH 公鑰,你要的曲線是 x25519,參數(shù)都在明文中。你驗一下簽名,確認是我發(fā)的,沒人改過?!睘槭裁催@一步對中間人至關(guān)重要?如果中間人想冒充服務器,它必須在這一步之后,自己也生成一對 DH 公私鑰,把自己的公鑰發(fā)給客戶端。但是——中間人用自己的私鑰簽名后,客戶端用真正的服務器的公鑰(從第三步的證書里拿到的)去驗證簽名時,會失敗。因為簽名用的是中間人的私鑰,不是服務器的私鑰。這就是 CA 信任鏈在 ECDHE 中的關(guān)鍵作用。第五步:ClientKeyExchange(客戶端奉上鑰匙)客戶端發(fā):1 字節(jié):0x10(ClientKeyExchange) 3 字節(jié):長度 內(nèi)容:根據(jù)不同的密鑰交換算法,內(nèi)容完全不同RSA 模式(現(xiàn)在很少用了):客戶端生成 48 字節(jié)的 Pre-Master Secret:Pre-Master Secret: 2 字節(jié):客戶端支持的 TLS 版本(比如 0x0303) 46 字節(jié):純隨機數(shù)然后用服務器的公鑰加密這 48 字節(jié),發(fā)過去。服務器用自己的私鑰解密,拿到 Pre-Master Secret。DH/ECDHE 模式(現(xiàn)代主流,TLS 1.3 強制用):客戶端發(fā)送自己的 DH 公鑰。然后雙方各自用自己的私鑰 + 對方的公鑰,計算出相同的 Pre-Master Secret。關(guān)鍵區(qū)別:RSA 模式下,Pre-Master Secret 是"客戶端生成 → 加密發(fā)送 → 服務器解密"——它在網(wǎng)絡上傳輸過(雖然是加密的)ECDHE 模式下,Pre-Master Secret 是"雙方各自用對方的公鑰 + 自己的私鑰在本地計算出"——它從未在網(wǎng)絡上傳輸過第二個模式的最大優(yōu)勢:Forward Secrecy(前向安全性)。什么意思呢?如果用 RSA 模式,五年后攻擊者拿到了服務器的私鑰——它可以解密五年前保存的加密的 Pre-Master Secret,進而推算出五年前的會話密鑰,解密五年前的通信內(nèi)容。如果用 ECDHE 模式,五年前的通信的 DH 臨時密鑰早銷毀了。即使拿到了服務器的主私鑰,也計算不出過去的 Pre-Master Secret。也就解密不了過去的通信。這就是"前向安全"——確保即使今天被攻破,過去的通信仍然安全。第六步:Change Cipher Spec + Finished雙方各自做了一大堆數(shù)學計算:ClientRandom + ServerRandom + Pre-Master Secret → PRF(偽隨機函數(shù)) → Master Secret(48 字節(jié)) → 密鑰導出函數(shù) → 6 個會話密鑰: - 客戶端寫密鑰(AES-128-GCM 的 key) - 服務端寫密鑰 - 客戶端 IV - 服務端 IV - 客戶端 HMAC 密鑰 - 服務端 HMAC 密鑰這個過程在代碼里的偽代碼表示:// TLS 1.2 密鑰派生偽代碼 function derive_keys(client_random[32], server_random[32], premaster_secret[48]): // 第一步:用 PRF 從 Pre-Master Secret 派生出 Master Secret master_secret[48] = PRF( secret: premaster_secret, label: "master secret", seed: client_random + server_random ) // 第二步:用 PRF 從 Master Secret 派生出 6 個會話密鑰 key_b