前一篇介紹了 SSL/TLS 的身份認證機制。這個機制是爲了防止攻擊者經過【篡改】網絡傳輸數據,來假冒身份,以達到「中間人攻擊/MITM」的目的。
而今天要聊的「密鑰協商機制」是:(在身份認證的前提下)如何規避【偷窺】的風險。
通俗地說,即便有攻擊者在偷窺你與服務器的網絡傳輸,客戶端(client)依然能夠利用「密鑰協商機制」與服務器端(server)協商出一個用來加密應用層數據的密鑰(也稱「會話密鑰」)。
html
俺總結了一下,大體有以下幾種類型:
python
原理:
拿到公鑰的一方先生成隨機的會話密鑰,而後利用公鑰加密它;再把加密結果發給對方,對方用私鑰解密;因而雙方都獲得了會話密鑰。
舉例:
RSA
git
原理:
這個原理比較複雜,一兩句話說不清楚,待會兒聊到 DH 的那個章節會詳談。
舉例:
DH 算法及其變種
算法
原理:
既然雙方已經有共享的祕密(這個「祕密」可能已是一個密鑰,也可能只是某個密碼/password),只須要根據某種生成算法,就可讓雙方產生相同的密鑰(而且密鑰長度能夠任意指定)
舉例:
PSK 和 SRP(可能不少同窗沒聽過這倆玩意兒。別擔憂,本文後續部分有介紹)
安全
這大概是 SSL 最古老的密鑰協商方式——早期的 SSLv2 只支持一種密鑰協商機制,就是它。(前一篇)介紹身份認證重要性的時候,也是拿 RSA 來演示。
RSA 是一種【非】對稱加密算法。在本系列第1篇的背景知識介紹中,已經聊過這種算法的特色——加密和解密用使用【不一樣的】密鑰。而且「非對稱加密算法」既能夠用來作「加密/解密」,還能夠用來作「數字簽名」。
服務器
(下列步驟只闡述原理,具體的協議細節在下一篇講)
1. 客戶端連上服務端
2. 服務端發送 CA 證書給客戶端
3. 客戶端驗證該證書的可靠性
4. 客戶端從 CA 證書中取出公鑰
5. 客戶端生成一個隨機密鑰 k,並用這個公鑰加密獲得 k'
6. 客戶端把 k' 發送給服務端
7. 服務端收到 k' 後用本身的私鑰解密獲得 k
8. 此時雙方都獲得了密鑰 k,協商完成。
網絡
攻擊方式1
攻擊者雖然能夠監視網絡流量並拿到公鑰,可是【沒法】經過公鑰推算出私鑰(這點由 RSA 算法保證)
攻擊方式2
攻擊者雖然能夠監視網絡流量並拿到 k',可是攻擊者沒有私鑰,【沒法解密】 k',所以也就沒法獲得 k
性能
攻擊方式1
若是攻擊者在第2步篡改數據,僞造了證書,那麼客戶端在第3步會發現(這點由證書體系保證)
攻擊方式2
若是攻擊者在第6步篡改數據,僞造了k',那麼服務端收到假的k'以後,解密會失敗(這點由 RSA 算法保證)。服務端就知道被攻擊了。
加密
DH 算法又稱「Diffie–Hellman 算法」。這是兩位數學牛人的名稱,他們創立了這個算法。該算法用來實現【安全的】「密鑰交換」。它能夠作到——「通信雙方在徹底沒有對方任何預先信息的條件下經過不安全信道建立起一個密鑰」。這句話比較繞口,通俗地說,能夠歸結爲兩個優勢:
1. 通信雙方事先【不】須要有共享的祕密。
2. 用該算法協商密碼,即便協商過程當中被別人全程偷窺(好比「網絡嗅探」),偷窺者也【沒法】知道協商得出的密鑰是啥。
可是 DH 算法自己也有缺點——它不支持認證。也就是說:它雖然能夠對抗「偷窺」,卻沒法對抗「篡改」,天然也就沒法對抗「中間人攻擊/MITM」(在本系列的前一篇,俺已經強調過了——缺少身份認證,【一定會】遭到「中間人攻擊/MITM」)。
爲了不遭遇 MITM 攻擊,DH 須要與其它簽名算法(好比 RSA、DSA、ECDSA)配合——靠簽名算法幫忙來進行身份認證。當 DH 與 RSA 配合使用,稱之爲「DH-RSA」,與 DSA 配合則稱爲「DH-DSA」,以此類推
反之,若是 DH 【沒有】配合某種簽名算法,則稱爲「DH-ANON」(ANON 是洋文「匿名」的簡寫)。此時會遭遇「中間人攻擊/MITM」。(具體的中間人攻擊手法,能夠參見本系列前一篇)
關於該算法的更多介紹,能夠參見維基百科(這個條目)。
spa
(若是你屬於那種「看了數學公式就犯暈的人」,能夠直接略過本小節,不影響你看後續的章節)
從概念上講:DH 依賴的是:求解「離散對數問題」的複雜性。具體的算法以下:
通信雙方(張3、李四)須要先約定好算法參數(algorithm parameters):一個素數 p 做爲模數,一個素數 g 做爲基數(g 也稱爲「生成元」)。這兩個算法參數是能夠對外公開滴。
對於張三而言,須要先想好一個祕密的天然數 a 做爲私鑰(不能公開),而後計算 A = ga mod p 做爲本身的公鑰(能夠公開)。
對李四而言也相似,先想好一個祕密的天然數 b 做爲私鑰(不能公開),而後計算 B = gb mod p 做爲本身的公鑰(能夠公開)。
張三和李四互相交換各自的公鑰。
而後張三計算出 k = Ba mod p,李四計算出 k = Ab mod p
該算法至少確保了以下幾點:
1. 張三和李四分別計算出來的 k 一定是一致的
2. 張三和李四都沒法根據已知的數來推算出對方的私鑰(張三沒法推算出 b,李四沒法推算出 a)
3. 對於一個旁觀者(偷窺者),雖然能看到 p,g,A,B,可是沒法推算出 a 和 b(就是說,旁觀者沒法推算出雙方的私鑰),天然也沒法推算出 k
舉例
前面說得都是符號,比較抽象。下面拿具體數字舉例:
假設約定的算法參數:模數是 97,基數是 3
張三用的私鑰是 6,李四用的私鑰是 21,用 python 代碼演示以下(python 語言用兩個連續星號表示「冪運算」,用百分號表示「取模運算」):
p = 97
g = 3
a = 6
b = 21
A = (g**a) % p
B = (g**b) % p
print((B**a) % p) # 47
print((A**b) % p) # 47
最後打印出來的兩個 47 就是雙方都計算出了【相同的】結果(這個數值能夠用做以後的「會話密鑰」)
上面由於是舉例,用的數字都比較小。在實戰中須要注意以下幾點,以下降被攻擊的風險。
1. p 必須是質數且足夠大(至少300位)
2. a,b 也要足夠大(至少100位),且必須是隨機生成。
3. g 必須是質數,【不】須要很大,好比 2 或 3 或 5 均可以。g 若是太大並【不能】顯著提高安全性,反而會影響性能。
(下列步驟只闡述原理,具體的協議細節在下一篇講)
1. 客戶端先連上服務端
2. 服務端生成一個隨機數 s 做爲本身的私鑰,而後根據算法參數計算出公鑰 S(算法參數一般是固定的)
3. 服務端使用某種簽名算法把「算法參數(模數p,基數g)和服務端公鑰S」做爲一個總體進行簽名
4. 服務端把「算法參數(模數p,基數g)、服務端公鑰S、簽名」發送給客戶端
5. 客戶端收到後驗證簽名是否有效
6. 客戶端生成一個隨機數 c 做爲本身的私鑰,而後根據算法參數計算出公鑰 C
7. 客戶端把 C 發送給服務端
8. 客戶端和服務端(根據上述 DH 算法)各自計算出 k 做爲會話密鑰
嗅探者能夠經過監視網絡傳輸,獲得算法參數(模數p,基數g)以及雙方的公鑰,可是【沒法】推算出雙方的私鑰,也【沒法】推算出會話密鑰(這是由 DH 算法在數學上保證的)
攻擊方式1
攻擊者能夠第4步篡改數據(修改算法參數或服務端公鑰)。但由於這些信息已經進行過數字簽名。篡改以後會被客戶端發現。
攻擊方式2
攻擊者能夠在第7步篡改客戶端公鑰。這步沒有簽名,服務端收到數據後不會發現被篡改。可是,攻擊者篡改以後會致使「服務端與客戶端生成的會話密鑰【不一致】」。在後續的通信步驟中會發現這點,並致使通信終止。
(下一篇講具體協議的時候會提到:協議初始化/握手階段的末尾,雙方都會向對方發送一段「驗證性的密文」,這段密文用各自的會話密鑰進行【對稱】加密,若是雙方的會話密鑰不一致,這一步就會失敗,進而致使握手失敗,鏈接終止)
DH 算法有一個變種,稱之爲 ECDH(全稱是「Elliptic Curve Diffie-Hellman」)。維基條目在「這裏」
它與 DH 相似,差異在於:
DH 依賴的是——求解「離散對數問題」的困難。
ECDH 依賴的是——求解「橢圓曲線離散對數問題」的困難。
ECDH 的數學原理比 DH 更復雜。考慮到本文讀者大都【不是】數學系出身,俺就不展開了。
ECDH 跟 DH 同樣,也是【無認證】的。一樣須要跟其它簽名算法(好比 RSA、DSA、ECDSA)配合。
剛纔介紹的 DH 和 ECDH,其密鑰是持久的(靜態的)。也就是說,通信雙方生成各自的密鑰以後,就長時間用下去。這麼幹固然比較省事兒(節約性能),可是存在某種安全隱患——沒法作到「前向保密」(洋文是「forward secrecy」)。
爲了作到「前向保密」,採用「臨時密鑰」(洋文是「ephemeral key」)的方式對 DH 和 ECDH 進行改良。因而獲得兩種新的算法——DHE 和 ECDHE。(這兩種新算法的名稱,就是在原有名稱後面加上字母 E 表示 ephemeral)。其實算法仍是同樣的,只是對每一個會話都要從新協商一次密鑰,且密鑰用完就丟棄。
(估計不少同窗不太瞭解「前向保密」這個概念。俺會在本系列中單獨開一帖,介紹「前向保密」的概念及其好處)
PSK 是洋文「Pre-Shared Key」的縮寫。顧名思義,就是【預先】讓通信雙方共享一些密鑰(一般是【對稱加密】的密鑰)。所謂的【預先】,就是說,這些密鑰在 TLS 鏈接還沒有創建以前,就已經部署在通信雙方的系統內了。
這種算法用的很少,它的好處是:
1. 不須要依賴公鑰體系,不須要部屬 CA 證書。
2. 不須要涉及非對稱加密,TLS 協議握手(初始化)時的性能好於前述的 RSA 和 DH。
更多介紹能夠參見維基百科,連接在「這裏」。
(因爲 PSK 用的很少,下面只簡單介紹一下步驟,讓大夥兒明白其原理)
在通信【以前】,通信雙方已經預先部署了若干個共享的密鑰。
爲了標識多個密鑰,給每個密鑰定義一個惟一的 ID
協商的過程很簡單:客戶端把本身選好的密鑰的 ID 告訴服務端。
若是服務端在本身的密鑰池子中找到這個 ID,就用對應的密鑰與客戶端通信;不然就報錯並中斷鏈接。
使用這種算法,在協商密鑰的過程當中交換的是密鑰的標識(ID)而【不是】密鑰自己。
就算攻擊者監視了全過程,也沒法知曉密鑰啥。
PSK 能夠單獨使用,也能夠搭配簽名算法一塊兒使用。
對於單獨使用
若是攻擊者篡改了協商過程當中傳送的密鑰 ID,要麼服務端發現 ID 無效(協商失敗),要麼服務端獲得的 ID 與客戶端不一致,在後續的通信步驟中也會發現,並致使通信終止。
(下一篇講具體協議的時候會提到:協議初始化/握手階段的末尾,雙方都會向對方發送一段「驗證性的密文」,這段密文用各自的會話密鑰進行【對稱】加密,若是雙方的會話密鑰不一致,這一步就會失敗,進而致使握手失敗,鏈接終止)
對於搭配簽名算法
若是攻擊者篡改了協商過程當中傳送的密鑰 ID,驗證簽名會失敗
PSK 與 RSA 具備某種類似性——既能夠用來搞「密鑰協商」,也能夠用來搞「身份認證」。
因此,PSK 能夠跟 DH(及其變種)進行組合。例如:DHE-PSK、ECDHE-PSK
關於 PSK 的更多細節,能夠參見 RFC4279
SRP 是洋文「Secure Remote Password」的縮寫。這個算法有點相似於剛纔提到的 PSK——只不過 client/server 雙方共享的是比較人性化的密碼(password)而不是密鑰(key)。該算法採用了一些機制(鹽/salt、隨機數)來防範「嗅探/sniffer」或「字典猜解攻擊」或「重放攻擊」。
這個算法應該用得不多——OpenSSL 直到2012年纔開始支持該算法。因此俺這裏就不展開了。有興趣的同窗能夠去看 RFC2945 的協議描述。
算法組合 | 密鑰交換 | 身份認證 | 是否會遭遇 中間人攻擊 |
是否具有 前向保密 |
SSL 2.0 | SSL 3.0 | TLS 1.0 | TLS 1.1 | TLS 1.2 | TLS 1.3 (草案) |
---|---|---|---|---|---|---|---|---|---|---|
RSA | RSA | RSA | 否 | 否 | 是 | 是 | 是 | 是 | 是 | 否 |
DH-RSA | DH | RSA | 否 | 否 | 否 | 是 | 是 | 是 | 是 | 否 |
DH-DSA | DH | DSA | 否 | 否 | 否 | 是 | 是 | 是 | 是 | 否 |
DHE-RSA | DHE | RSA | 否 | 是 | 否 | 是 | 是 | 是 | 是 | 是 |
DHE-DSA | DHE | DSA | 否 | 是 | 否 | 是 | 是 | 是 | 是 | 是 |
ECDH-RSA | ECDH | RSA | 否 | 否 | 否 | 否 | 是 | 是 | 是 | 否 |
ECDH-ECDSA | ECDH | ECDSA | 否 | 否 | 否 | 否 | 是 | 是 | 是 | 否 |
ECDHE-RSA | DHE | RSA | 否 | 是 | 否 | 否 | 是 | 是 | 是 | 是 |
ECDHE-ECDSA | DHE | ECDSA | 否 | 是 | 否 | 否 | 是 | 是 | 是 | 是 |
PSK | PSK | PSK | 否 | 否 | 否 | 否 | 是 | 是 | 是 | ? |
PSK-RSA | PSK | RSA | 否 | 否 | 否 | 否 | 是 | 是 | 是 | ? |
DHE-PSK | DHE | PSK | 否 | 是 | 否 | 否 | 是 | 是 | 是 | ? |
ECDHE-PSK | DHE | PSK | 否 | 是 | 否 | 否 | 是 | 是 | 是 | ? |
SRP | SRP | SRP | 否 | 否 | 否 | 否 | 是 | 是 | 是 | ? |
SRP-RSA | SRP | RSA | 否 | 否 | 否 | 否 | 是 | 是 | 是 | ? |
SRP-DSA | SRP | DSA | 否 | 否 | 否 | 否 | 是 | 是 | 是 | ? |
DH-ANON | DH | 無 | 是 | 否 | 否 | 是 | 是 | 是 | 是 | 否 |
ECDH-ANON | ECDH | 無 | 是 | 否 | 否 | 否 | 是 | 是 | 是 | 否 |
(截至本文發佈時,TLS 1.3 還處於「草案」階段,還沒有正式發佈。等到它正式發佈,俺會把上述表格的最後一列再補充一下)