HTTPS 常用的密鑰交換算法有兩種,分別是 RSA 和 ECDHE 算法。
其中,RSA 是比較傳統的密鑰交換算法,它不具備前向安全的性質,因此現在很少服務器使用的。而 ECDHE 算法具有前向安全,所以被廣泛使用。
我在上一篇已經介紹了 https原理--RSA密鑰協商算法,這一篇就介紹 ECDHE 算法。
一、離散對數
ECDHE 密鑰協商算法是 DH 算法演進過來的,所以我們先從 DH 算法說起。
DH 算法是非對稱加密算法, 因此它可以用於密鑰交換,該算法的核心數學思想是離散對數。這里簡單提一下它的數學公式。
離散對數是「離散 + 對數」的兩個數學概念的組合,所以我們先來復習一遍對數。
要說起對數,必然要說指數,因為它們是互為反函數,指數就是冪運算,對數是指數的逆運算。
舉個栗子,如果以 2 作為底數,那么指數和對數運算公式,如下圖所示:
那么對於底數為 2 的時候, 32 的對數是 5,64 的對數是 6,計算過程如下:
對數運算的取值是可以連續的,而離散對數的取值是不能連續的,因此也以「離散」得名,
離散對數是在對數運算的基礎上加了「模運算」,也就說取余數,對應編程語言的操作符是「%」,也可以用 mod 表示。離散對數的概念如下圖:
上圖的,底數 a 和模數 p 是離散對數的公共參數,也就說是公開的,b 是真數,i 是對數。知道了對數,就可以用上面的公式計算出真數。但反過來,知道真數卻很難推算出對數。
特別是當模數 p 是一個很大的質數,即使知道底數 a 和真數 b ,在現有的計算機的計算水平是幾乎無法算出離散對數的,這就是 DH 算法的數學基礎。
1.DH 密鑰協商算法
認識了離散對數,我們來看看 DH 算法原理。
Diffie-He llman 算法,簡稱DH 算法, 是Whitfield Diffie 和Martin Hellman 在1976 年公布的一個公開密鑰算法,它的歷史比RSA 公開密鑰算法更悠久。
使用RSA 密鑰協商算法傳輸會話密鑰的時候,會話密鑰完全由客戶端控制,並沒有服務器的參與,所以叫作密鑰傳輸。而DH 算法確切地說, 實現的是密鑰交換或者密鑰協商, DH 算法在進行密鑰協商的時候,通信雙方的任何一方無法獨自計算出一個會話密鑰,通信雙方各自保留一部分關鍵信息,再將另外一部分信息告訴對方,雙方有了全部信息才能共同計算出相同的會話密鑰。客戶端和服務器端協商會話密鑰的時候,需要互相傳遞消息, 消息即使被挾持,攻擊者也無法計算出會話密鑰,因為攻擊者沒有足夠的信息(通信雙方各自保留的信息〉計算出同樣的會話密鑰。DH 算法的主要原理和優勢大概就是如此, 理解起來可能抽象了一點,下面看看DH算法的內部結構和原理,和RSA 算法一樣, 不用過於理解算法的內部實現。
1 )參數文件
在使用DH 算法之前, 先要生成一些公共參數,這些參數是公開的,無須擔心攻擊者能夠看到這些參數值,這些參數可以由客戶端或者服務器端生成, 一般由服務器端生成。參數在協商密鑰之前必須發給對端。
參數有兩個,分別是p 和g, p 是一個很大的質數,建議長度在1024 比特以上,這個長度也決定了DH 算法的安全程度, g 表示為一個生成器,這個值很小,可以是2 或者5。
通過參數,服務器端和客戶端會生各自生成一個DH 密鑰對,私鑰需要保密。
2) DH 密鑰協商算法密鑰交換過程
根據私鑰生成的方式,DH 算法分為兩種實現:
- static DH 算法,這個是已經被廢棄了;
- DHE 算法,現在常用的,下圖描述了DHE 算法處理過程。
-
通信雙方的任何一方可以生成公共參數p 和g,這兩個數是公開的,被截獲了也沒有任何關系, 一般情況下由通信雙方的服務器端計算。
客戶端連接服務器端,服務器端將參數發送給客戶端。 -
客戶端根據公開參數生成一個隨機數a ,這個隨機數是私鑰,只有客戶端知道,且不會進行發送, 然后計算Yc= (g ^a) mod p , Yc 就是公鑰,需要發送給服務器端。
-
服務器端根據公開參數生成一個隨機數b ,這個隨機數是私鑰,需要服務器端保密,然后計算Ys = (g^b) mod p, Ys 是公鑰, 需要發送給客戶端。
-
客戶端發送Y e 數值給服務器端,服務器端計算Z = (Yc^b) mod p 。
-
服務器端發送Ys 數值給發送方, 客戶端計算Z=(Ys^a)mod p 。
-
服務器端和客戶端生成的Z 就是會話密鑰,協商完成。
這里的關鍵點就是私鑰a 和b 不應該泄露,分別由通信雙方維護,另外Ys 和Yc 進行互換才能完成協商,這兩個值被截獲對攻擊者來說沒有任何價值。換句話說,只要私鑰不發生泄露,攻擊者即使有了Ys 和Yc 也不會計算出會話密鑰。
看到冪運算和求模過程,就知道DH 算法和RSA 算法一樣,如果需要破解密鑰, 就必須面臨離散對數和因式分解問題。和其他公開密鑰算法一樣,只要確保一定的密鑰長度,DH 算法具有很高的安全性。RSA 和DH 密鑰對一樣能夠受到暴力攻擊,提高密鑰對的長度能夠有效避免攻擊。
可以看到,整個密鑰協商過程中,客戶端和服務端公開了 4 個信息:p、g、Yc、Ys,其中 p、g是算法的參數,Yc、Ys 是公鑰,而 a、b 是雙方各自保管的私鑰,黑客無法獲取這 2 個私鑰,因此黑客只能從公開的 p、g、Yc、Ys 入手,計算出離散對數(私鑰)。
前面也多次強調, 根據離散對數的原理,如果p 是一個大數,在現有的計算機的計算能力是很難破解出 私鑰 a、b 的,破解不出私鑰,也就無法計算出會話密鑰,如果量子計算機出來了,那就有可能被破解,當然如果量子計算機真的出來了,那么密鑰協商算法就要做大的升級了。因此 DH 密鑰交換是安全的。
2.DHE 密鑰協商算法
上文說過,根據私鑰生成的方式,DH 算法分為兩種實現:
- static DH 算法,這個是已經被廢棄了;
- DHE 算法,現在常用的;
靜態DH 算法, p 和g 兩個參數永遠是固定的,而且服務器的公鑰C Ys )也是固定的。和RSA 密鑰協商算法一樣,一旦服務器對應的DH 私鑰泄露,就不能提供前向安全性。靜態DH 算法的好處就是避免在初始化連接時服務器頻繁生成參數p 和g ,因為該過程是非常消耗CPU 運算的。
於是,DH 交換密鑰時就只有客戶端的公鑰是變化,而服務端公鑰是不變的,那么隨着時間延長,黑客就會截獲海量的密鑰協商過程的數據,因為密鑰協商的過程有些數據是公開的,黑客就可以依據這些數據暴力破解出服務器的私鑰,然后就可以計算出會話密鑰了,於是之前截獲的加密數據會被破解,所以 static DH 算法不具備前向安全性。
既然固定一方的私鑰有被破解的風險,那么干脆就讓雙方的私鑰在每次密鑰交換通信時,都是隨機生成的、臨時的,這個方式也就是 DHE 算法,E 全稱是 ephemeral(臨時性的)。下圖形象得說明了DHE算法密鑰交換過程
所以,即使有個牛逼的黑客破解了某一次通信過程的私鑰,其他通信過程的私鑰仍然是安全的,因為每個通信過程的私鑰都是沒有任何關系的,都是獨立的,這樣就保證了「前向安全」。
3.ECDHE 密鑰協商算法
DHE 算法由於計算性能不佳,因為需要做大量的乘法,為了提升 DHE 算法的性能,所以就出現了現在廣泛用於密鑰交換算法 —— ECDHE 算法。
ECDHE 是使用橢圓曲線(ECC)的 DH(Diffie-Hellman)算法,ECDHE 算法是在 DHE 算法的基礎上利用了 ECC 橢圓曲線特性,可以用更少的計算量計算出公鑰,以及最終的會話密鑰。
客服端和服務器使用 ECDHE 密鑰交換算法的過程:
- 雙方事先確定好使用哪種橢圓曲線,和曲線上的基點 G,這兩個參數都是公開的;
- 雙方各自隨機生成一個隨機數作為私鑰d,並與基點 G相乘得到公鑰Q(Q = dG),此時客服端的公私鑰為 Q1 和 d1,服務器的公私鑰為 Q2 和 d2;
- 雙方交換各自的公鑰,最后客戶端計算點(x1,y1) = d1Q2,服務器計算點(x2,y2) = d2Q1,由於橢圓曲線上是可以滿足乘法交換和結合律,所以 d1Q2 = d1d2G <==> d2Q1= d2d1G ,因此雙方的 x 坐標是一樣的,x 還不是最終的會話密鑰,它是一個預主密鑰。
這個過程中,雙方的私鑰都是隨機、臨時生成的,都是不公開的,即使根據公開的信息(橢圓曲線、公鑰、基點 G)也是很難計算出橢圓曲線上的離散對數(私鑰)。
4.RSA算法、DH算法與ECDH算法的安全性對比
為了保證DH的密鑰對不被破解,提升安全性的主要手段就是增加密鑰對的長度,但是長度越長,性能越低。公開密鑰算法是一個O(n)操作,n就是密鑰對的長度,n越小,操作越快。為了解決性能問題,需要了解下橢圓曲線密碼學( Elliptic Curve Cryptography ) ,簡稱為ECC 。
ECC 是新一代的公開密鑰算法,主要的優點就是安全性,極短的密鑰能夠提供很大的安全性。比如224 比特的ECC 密鑰和2048 比特的RSA 密鑰可以達到同樣的安全水平,由於ECC 密鑰具有很短的長度,運算速度非常快。ECC 基於非常復雜的算法,到目前位置,對於ECC 進行逆操作還是很難的,數學上被證明是不可破解的, ECC 算法的優勢就是性能和安全性非常高。
在具體應用的時候, ECC 可以結合其他公開密鑰算法形成更快、更安全的公開密鑰算法,比如結合DH 密鑰協商算法組成ECDH 密鑰協商算法,結合數字簽名DSA 算法組成ECDSA 數字簽名算法。下表是三個主流算法的密鑰長度對比:
通過下表可以看出,一個80 比特的對稱加密算法密鑰長度安全性相當於10 24 比特的公開密鑰算法密鑰、相當於160 比特的ECC 橢圓曲線密鑰。
必須明白密碼學算法不同長度密鑰的安全性,在使用密碼學算法的時候,也要選擇安全長度的密鑰,我們可以采用ECRYPT II 的標准,下表是主流算法的推薦密鑰安全長度:
需要注意的是,需要長期關注密鑰的長度,隨着時間的推移,目前安全的密鑰長度未來可能就不安全了,如果特定長度的密鑰不再安全,必須立刻更新密鑰。
二、ECDHE 密鑰協商握手過程
知道了 ECDHE 算法基本原理后,我們就結合實際的情況來看看。
我用 Wireshark 工具抓了用 ECDHE 密鑰協商算法的 TSL 握手過程,可以看到是四次握手:
細心的小伙伴應該發現了,使用了 ECDHE,在 TLS 第四次握手前,客戶端就已經發送了加密的 HTTP 數據,而對於 RSA 握手過程,必須要完成 TLS 四次握手,才能傳輸應用數據。
所以,ECDHE 相比 RSA 握手過程省去了一個消息往返的時間,這個有點「搶跑」的意思,它被稱為是「TLS False Start」,跟「TCP Fast Open」有點像,都是在還沒連接完全建立前,就發送了應用數據,這樣便提高了傳輸的效率。
接下來,分析每一個 ECDHE 握手過程。
1.TLS 第一次握手
客戶端首先會發一個「Client Hello」消息,消息里面有客戶端使用的 TLS 版本號、支持的密碼套件列表,支持的壓縮算法,以及生成的隨機數(Client Random)。
2.TLS 第二次握手
服務端收到客戶端的「打招呼」,同樣也要回禮,會返回「Server Hello」消息,消息面有服務器確認的 TLS 版本號,也給出了一個隨機數(Server Random),然后從客戶端的密碼套件列表選擇了一個合適的密碼套件和壓縮算法(這里壓縮算法為null,表示不壓縮),目前不建議進行TLS壓縮,如果壓縮,可能會被攻擊,比如CRIME攻擊,禁止TLS壓縮並不會影響HTTPS網站的性能,所以服務器應該禁止TLS壓縮。
Nginx服務器高版本已經關閉TLS壓縮:
- Nginx1.1.6、1.0.9(基於OpenSSL1.0.0以后的版本)以后的版本禁止使用TLS壓縮;
- Nginx1.3.2+、1.2.2(基於OpenSSL1.0.0以前的版本)以后的版本禁止使用TLS壓縮。
不過,這次選擇的密碼套件就和 RSA 不一樣了,我們來分析一下這次的密碼套件的意思。
「 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384」
- 密鑰協商算法使用 ECDHE;
- 簽名算法使用 RSA;
- 握手后的通信使用 AES 對稱算法,密鑰長度 256 位,分組模式是 GCM;
- 摘要算法使用 SHA384;
接着,服務端為了證明自己的身份,發送「Certificate」消息,會把證書也發給客戶端。
這一步就和 RSA 握手過程有很大到區別了,因為服務端選擇了 ECDHE 密鑰協商算法,所以會在發送完證書后,發送「Server Key Exchange」消息。
這個過程服務器做了三件事:
- 選擇了名為 named_curve 的橢圓曲線,選好了橢圓曲線相當於橢圓曲線基點 G 也定好了,這些都會公開給客戶端;
- 生成隨機數作為服務端橢圓曲線的私鑰,保留到本地;
- 根據基點 G 和私鑰計算出服務端的橢圓曲線公鑰,這個會公開給客戶端。
為了保證這個橢圓曲線的公鑰不被第三方篡改,服務端會用 RSA 簽名算法給服務端的橢圓曲線公鑰做個簽名。
隨后,就是「Server Hello Done」消息,服務端跟客戶端表明:“這些就是我提供的信息,打招呼完畢”。
至此,TLS 兩次握手就已經完成了,目前客戶端和服務端通過明文共享了這幾個信息:Client Random、Server Random 、使用的橢圓曲線、橢圓曲線基點 G、服務端橢圓曲線的公鑰,這幾個信息很重要,是后續生成會話密鑰的材料。
3.TLS 第三次握手
客戶端收到了服務端的證書后,自然要校驗證書是否合法,如果證書合法,那么服務端到身份就是沒問題的。校驗證書到過程,會走證書鏈逐級驗證,確認證書的真實性,再用證書的公鑰驗證簽名,這樣就能確認服務端的身份了,確認無誤后,就可以繼續往下走。
客戶端會生成一個隨機數作為客戶端橢圓曲線的私鑰,然后再根據服務端前面給的信息,生成客戶端的橢圓曲線公鑰,然后用「Client Key Exchange」消息發給服務端。
至此,雙方都有對方的橢圓曲線公鑰、自己的橢圓曲線私鑰、橢圓曲線基點 G。於是,雙方都就計算出點(x,y),其中 x 坐標值雙方都是一樣的,x 是一個預主密鑰。
還記得 TLS 握手階段,客戶端和服務端都會生成了一個隨機數傳遞給對方嗎?
最終的會話密鑰,就是用「客戶端隨機數 + 服務端隨機數 + x(ECDHE 算法算出的共享密鑰) 」三個材料生成的。
之所以這么麻煩,是因為 TLS 設計者不信任客戶端或服務器「偽隨機數」的可靠性,為了保證真正的完全隨機,把三個不可靠的隨機數混合起來,那么「隨機」的程度就非常高了,足夠讓黑客計算出最終的會話密鑰,安全性更高。
算好會話密鑰后,客戶端會發一個「Change Cipher Spec」消息,告訴服務端后續改用對稱算法加密通信。
接着,客戶端會發「Encrypted Handshake Message」消息,把之前發送的數據做一個摘要,再用對稱密鑰加密一下,讓服務端做個驗證,驗證下本次生成的對稱密鑰是否可以正常使用。
4.TLS 第四次握手
最后,服務端也會有一個同樣的操作,發「Change Cipher Spec」和「Encrypted Handshake Message」消息,如果雙方都驗證加密和解密沒問題,那么握手正式完成。於是,就可以正常收發加密的 HTTP 請求和響應了。
三、總結
RSA 和 ECDHE 握手過程的區別:
- RSA 密鑰協商算法「不支持」前向保密(ECDH和DH也不支持),ECDHE 密鑰協商算法「支持」前向保密(DHE支持);
- 使用了 RSA 密鑰協商算法,TLS 完成四次握手后,才能進行應用數據傳輸,而對於 ECDHE 算法,客戶端可以不用等服務端的最后一次 TLS 握手,就可以提前發出加密的 HTTP 數據,節省了一個消息的往返時間;
- 使用 ECDHE, 在 TLS 第 2 次握手中,會出現服務器端發出的「Server Key Exchange」消息,而 RSA 握手過程沒有該消息;
ECDH 和 ECDHE 握手過程的區別:
字面少了一個E,E代表了“臨時”,即在握手流程中,作為服務器端,ECDH少了一步計算Pb(服務端的DH公鑰)的過程,Pb用證書中的公鑰代替,而證書對應的私鑰就是Xb。由此可見,使用ECDH密鑰交換算法,服務器必須采用ECC證書;服務器不發送server key exchange報文,因為發送certificate報文時,證書本身就包含了Pb信息。
四、HTTP/1.2、HTTP/2
HTTP/2 中只能使用 TLSv1.2+,還禁用了幾百種 CipherSuite(詳見:TLS 1.2 Cipher Suite Black List)。實際上,HTTP/2 允許使用的 CipherSuite 必須采用具有前向安全性的密鑰交換算法,不允許使用 RSA 密鑰交換。這也是為什么 RSA Private Key 無法解密 HTTP/2 加密流量。