IM聊天系統安全手段之傳輸內容端到端加密技術

語言: CN / TW / HK

本文由融雲技術團隊分享,原題“網際網路通訊安全之端到端加密技術”,內容有較多修訂和改動。

1、引言

在上篇《IM聊天系統安全手段之通訊連線層加密技術》中,分享了關於通訊連線層加密的相關技術和實踐,包括在傳輸即時通訊訊息時啟用 TLS 鏈路加密(保證訊息在到達伺服器前無法被竊聽和篡改)、使用 CA 認證機制(杜絕中間人攻擊)等。

本篇將圍繞IM傳輸內容的安全問題,以實踐為基礎,為你分享即時通訊應用中的“端到端”加密技術。

2、系列文章

本文是IM通訊安全知識系列文章中的第11篇,此係列總目錄如下:

3、為什麼需要端到端加密?

上篇中提到的連線層加密技術,這是提升IM客戶端到伺服器之間資料傳輸的安全性手段,但是這並不能解決使用者間的通訊隱私性以及安全性風險。

因為在將資料傳輸到伺服器之後,所有有權訪問此伺服器的人,包括員工、供應商及其他有關人員(甚至黑客),都有可能讀取到使用者的資料。

有鑑於此,端到端加密技術在即時通訊IM領域被廣泛應用,包括WhatsApp、Signal、Telegram 等國外即時通訊軟體中都有使用。

PS:有關端到端加密的基礎知識,可以從這兩篇裡得到,建議詳讀:

4、端到端加密的技術設計思路

4.1 簡化版思路

說到端到端加密,我們首先想到的解決方案是:在傳送端傳送訊息前對整個訊息進行加密,接收端接收到訊息後進行解密。

如上這樣:訊息中轉伺服器就無法獲取我們的訊息內容了。

事實上:這確實是端到端加密中訊息收發的簡化版解決方案,只是我們在實際應用中要更加複雜,效果也更加安全。

4.2 如何安全地傳遞用於訊息加解密的金鑰

對於端到端加密,我們需要先解決的前置安全問題是:如何安全地傳遞用於訊息加解密的金鑰。

答案是:用非對稱加密的方式傳輸金鑰(與 SSL / TLS 中安全交換金鑰的方式類似)。

非對稱加密傳輸對稱加密金鑰的演算法,一般歸結兩種方式:

  • 1)一種是以 RSA、ECC 等為主(公鑰加密私鑰解密的方式,本質是加解密的演算法);
  • 2)另一種是以 DH、ECDH 為主的生成共享金鑰的方式(本質是通過計算協商一個共同的金鑰而不是加解密演算法)。

實際上:大部分即時通訊軟體中的端到端加密都採用生成共享金鑰的方式來傳輸會話金鑰。這是為什麼呢?

這就涉及到 DH 演算法(即 Diffie-Hellman 金鑰交換演算法),關於DH演算法的資料,有興趣可以詳讀《Diffie-Hellman金鑰協商演算法》,限於篇幅,這裡不專門討論。

Diffie-Hellman 金鑰交換演算法的安全性依賴於這樣一個事實:雖然計算以一個素數為模的指數相對容易,但計算離散對數卻很困難。對於大的素數,計算出離散對數幾乎是不可能的。

這裡簡要描述一下 DH 共享金鑰的過程如下:

其中“金鑰 S”即為最終的共享金鑰

4.3 採用共享金鑰的原因

端到端加密採用共享金鑰的方式來傳輸會話金鑰有如下幾個原因:

1)如果採用 RSA、ECC 等公鑰加密私鑰解密的方式傳輸金鑰,需要在建立會話時生成臨時金鑰,並通過對方公鑰加密後傳輸到接收端。

這就需要完全保證訊息的可靠性,如果該訊息在任何一個環節中丟失或損壞,則後續通訊都無法進行。或者,需要採用更為可靠的傳輸方案,通常做法為需要接收端線上,通過各種確認來保證這個可靠性。

而採用共享金鑰的方式則只需要知道對方的公鑰,就可以完成生成共享金鑰,並不一定需要對方線上。

2)如果已經生成的臨時對稱金鑰丟失,則需要重新協商金鑰。而採用共享金鑰的方式則只需要知道對方的公鑰,就可以完成生成共享金鑰,不需要重新協商。

3)採用公鑰加密私鑰解密的方式至少會比生成共享金鑰方式多一次交換對稱金鑰的通訊過程。

4)金鑰協商方式,不僅僅可以完成兩個點之間的金鑰協商,還可以延展到多人之間的共同協商出相同的金鑰,這樣能滿足多人群體溝通的需求。

5、端到端加密的初步實踐方案

我們結合對於 DH 演算法(即 Diffie-Hellman 金鑰交換演算法)這種共享金鑰方式的認知(即公鑰可隨意公開),先設計一個簡單的端到端訊息加密的過程。

這個過程的邏輯流程如下:

  • 1)在客戶端 APP 首次安裝時,基於伺服器公開的兩個全域性的引數,生成自己的 DH 公鑰和私鑰;
  • 2)將自己的公鑰上傳證書伺服器,證書伺服器上儲存使用者標識與其公鑰的關係。私鑰則儲存在客戶端上;
  • 3)首次給對方傳送訊息或首次接收到對方訊息時,便到證書伺服器查詢對方的公鑰;
  • 4)根據對方公鑰和自己的私鑰計算出共享金鑰;
  • 5)後續與對方所有的訊息都基於這個金鑰和相同的對稱加解密演算法進行加密解密操作。

端到端訊息加密過程示意:

至此:我們完成了一個簡單的端到端訊息加密方案,在這個方案中我們引入了一個第三方的用於儲存使用者公鑰的角色,這個角色的存在可以讓任何一方都不用關心對方的線上狀態,隨時給對方傳送加密過訊息,而訊息轉發伺服器無法解密訊息。

接下來,我們針對這個簡單方案存在的各種安全隱患問題,進行逐步分析和優化。

6、端到端加密實踐方案的進一步優化和演進

6.1 使用HMAC作為訊息完整性認證演算法

在訊息傳輸過程中,雙方需要確認彼此訊息的完整性,簡單的做法就是將訊息進行 Hash,得到的 Hash 值附加到訊息後,隨訊息一起傳送;對端接收後,同樣進行 Hash,來驗證訊息是否被篡改。

關鍵點在於不同資料得到的 Hash 值一定不同,其中帶金鑰的 Hash 值就是 MAC演算法。

另外,為了避免使用同樣的 Hash 函式對相同資料進行操作總是得出同樣的值,額外加入一個金鑰,這樣使用不同金鑰就可以得出不同的 MAC。當然,這個金鑰是兩個對端都知道的。

這樣,我們就得到了基於加密 Hash 的訊息完整性認證的演算法——Hash-based MAC(簡稱HMAC)。

基礎知識1:什麼是MAC演算法?

全稱Message Authentication Code,即訊息認證碼(帶金鑰的Hash函式)。在密碼學中,MAC是通訊實體雙方使用的一種驗證機制,是保證訊息資料完整性的一種工具。

MAC演算法的安全性依賴於Hash函式,故也稱帶金鑰的Hash函式。訊息認證碼是基於金鑰和訊息摘要“hash”所獲得的一個值,可用於資料來源發認證和完整性校驗。

使用 MAC 驗證訊息完整性的具體過程是:

  • 1)假設通訊雙方 A 和 B 共享金鑰 K,A用訊息認證碼演算法將 K 和訊息 M 計算出訊息驗證碼 Mac,然後將 Mac 和 M 一起傳送給 B;
  • 2)B 接收到 Mac 和 M 後,利用 M 和 K 計算出新的驗證碼 Mac*,若 Mac*和Mac 相等則驗證成功,證明訊息未被篡改。

由於攻擊者沒有金鑰 K,攻擊者修改了訊息內容後無法計算出相應的訊息驗證碼,因此 B 就能夠發現訊息完整性遭到破壞。

簡而言之就是:

  • 1)傳送者通過MAC演算法計算出訊息的MAC值,並和訊息一起發給收信者;
  • 2)收信者用同樣的MAC演算法計算收到的訊息的MAC值,並對比兩者。

下圖是原理示意:

基礎知識2:什麼是HMAC演算法?

HMAC是MAC演算法中的一種,其基於加密HASH演算法實現。任何加密HASH, 比如MD5、SHA256等,都可以用來實現HMAC演算法,其相應的演算法稱為HMAC-MD5、HMAC-SHA256等。

6.2 使用ECDH演算法替換DH演算法

DH 演算法是以離散對數的數學難題為基礎的,隨著計算機計算能力逐步增強,我們要不停地使用更大的數以增加破解難度,目前業界普遍認為至少需要使用 2048 位 DH 演算法才具備更好的安全性。

在此我們引入 ECDH 演算法替換 DH 演算法。ECDH 金鑰協商演算法是 ECC 演算法和 DH 金鑰交換原理結合使用。ECC 是建立在基於橢圓曲線的離散對數問題上的密碼體制。在相同破解難度下,ECC 具有更小長度的金鑰和更快的正向計算速度優勢。

我們系統上的 ECDH 可以直接採用目前公開的 sepc256kl 和 Curve25519 曲線,而無需服務再提供公開大數引數。

6.3 提升前向安全性

在訊息傳輸過程中,如果協商好的金鑰洩露了,就意味著所有資訊都將暴露於風險之下。

為了防止這種情況發生,我們需要每次加密使用的金鑰都與上一次不同,且不可以反向推導得出之前的金鑰。

此處引入一個 Hash 演算法:這個 Hash 演算法可以通過輸入一個金鑰匯出另外一個離散性更大的金鑰,每次傳送訊息時都是用上次的訊息金鑰進行 Hash 運算得出本次金鑰,由於 Hash 演算法具有單向不可逆的特性,因此就無法通過本次的金鑰推導之前的金鑰。

從感觀上,這就像一個棘輪,棘輪就是一種特殊的齒輪,他只能往一個方向轉下去,而不能往回轉。

我們先來感性認識一下棘輪:

在技術上,做到"只能往一個方向轉下去,而不能往回轉",是達到前向安全的關鍵。這就保證了,如果某一輪的金鑰被破解出來,但前面的金鑰是無法計算出來的,也就是前面的訊息無法被解密。

6.4 同時保證前向安全和後向安全性

出於極致的安全性要求,我們會同時考慮前向安全和後向安全。如何保證在某次通訊中,被破解出來的金鑰,不能破解出之前的訊息,而且在一定週期內,這個破解出來的金鑰將不會再起作用。

介於此我們再引入另外一個棘輪來保證其向後的安全性。這就是大名鼎鼎的 Signal protocol 中的雙棘輪演算法。

Signal protocol 是真正的端到端的通訊加密協議,號稱是世界上最安全的通訊協議,任何第三方包括伺服器都無法檢視通訊內容。

雙棘輪演算法包含一個 KDF 棘輪和一個 DH 棘輪。

KDF 全稱(Key derivation function) 金鑰匯出函式,用於從一個原始的金鑰匯出一個或多個金鑰。本質上就是 Hash 函式,通常用來將短密碼變成長密碼。另外 KDF 需要加“鹽”(salt),用於防彩虹表,出於 Hash 的特性,這個“鹽”的長度至少要大於 Hash 結果長度。

KDF (原金鑰,鹽) = 匯出金鑰

KDF 棘輪就是運用 KDF 演算法,設計出一種金鑰不斷變化的效果,流程如下:

首先:將初始金鑰使用 KDF 演算法匯出新的金鑰,新金鑰被切成兩部分,前半部分作為下一次 KDF 計算的輸入,後半部分作為訊息金鑰。

每迭代一次(也可以說棘輪步進一次),就會生成新的訊息金鑰。

由於 KDF 演算法的單向性,通過這條訊息的金鑰無法倒推出上一條訊息金鑰,這就保證了金鑰的前向安全。但是如果 KDF 中的鹽被掌握,那麼它就可以按照這種演算法計算出以後所有的訊息金鑰。

為了保證後向安全,就要設計一種方法,使每次迭代時引入的鹽是隨機的,從而保證每次的訊息金鑰是不可以向後推算的。

由前面介紹的 DH 演算法得知:兩對金鑰對可以通過 DH 協議生成一個安全的協商金鑰,如果更換其中一個金鑰對,新的協商金鑰也會變化。

根據這個方法:我們可以設計出一個安全更新鹽的方法。我們在證書伺服器增加一個臨時公鑰證書,這個臨時證書是按照接收雙方標識構建的臨時公鑰對,即每個人的每個單人會話都具備一個臨時公鑰。每進行一個訊息輪迴,就更新一次己方的臨時公鑰,同時根據另外一方的臨時公鑰和己方的私鑰進行協商,並將協商出的金鑰作為鹽,使得 KDF 棘輪演算法生成的訊息金鑰具有後向安全性。

在初始時我們無法預測出每個人所有的新二人會話:那麼我們就可以規定建立新的二人會話時,發起方首先生成一個新的臨時 DH 公私鑰對,並向伺服器上傳自己的臨時 DH 公鑰;其次傳送方用接收方公佈的長期公鑰與自己的臨時私鑰協商出金鑰作為訊息加密的金鑰,對訊息進行加密;最後接收方首次接收到訊息後用自己的長期公鑰和傳送方的臨時私鑰計算得出訊息金鑰,並在首次回覆訊息時生成臨時公私鑰,同時上傳臨時公鑰。

問題是:如果接收端不線上,而傳送端每條訊息都去更新己方的臨時公鑰證書,就會導致發出去的這些訊息,在接收端上線並收取後無法被正常解密。

為了解決這個問題,我們需要規定:只有在發出訊息並得到對方回覆後才更新臨時證書,若對方不回覆訊息則不去更新臨時證書。接收端能回覆訊息就表示其已經上線並接收完訊息,這樣就可以保證離線訊息或者訊息亂序也可以被對方正常解析。這種方法就是雙棘輪演算法中的另外一個 DH 棘輪。

6.5 更安全的金鑰交換協議—— X3DH

對比最初的方案,為了滿足訊息的前向安全和後向安全,我們增加了雙棘輪演算法,在原基礎方案上為每個人增加了一組會話級別臨時 DH 金鑰,每個人都擁有一個長期金鑰和一組臨時金鑰。

但是:由於長期金鑰無法被更換,所以方案依然存在著安全隱患。

因此:Signal protocol 設計了一種更為複雜和安全的 DH 金鑰交換過程,稱之為 X3DH(即 DH 協議的 3 倍擴充套件版)。

在 X3DH 協議裡,每個人都要建立 3 種金鑰對,分別如下:

  • 1)身份金鑰對(Identity Key Pair):一個長期的符合 DH 協議的金鑰對,使用者註冊時建立,與使用者身份繫結;
  • 2)已簽名的預共享金鑰(Signed Pre Key):一箇中期的符合 DH 協議的金鑰對,使用者註冊時建立,由身份金鑰簽名,並定期進行輪換,此金鑰可能是為了保護身份金鑰不被洩露;
  • 3)一次性預共享金鑰(One-Time Pre Keys):一次性使用的 Curve25519 金鑰對佇列,安裝時生成,不足時補充。

所有人都要將這 3 種金鑰對的公鑰上傳到伺服器上,以便其他人發起會話時使用。

假如 Alice 要給 Bob 傳送訊息,首先要和 Bob 確定訊息金鑰,流程大致如下:

  • 1)Alice 要建立一個臨時金鑰對(ephemeral key),我們設成 EPK-A,此金鑰對是為了後面棘輪演算法準備,在此處作用不大;
  • 2)Alice 從伺服器獲取 Bob 的三種金鑰對的公鑰:身份金鑰對IPK-B、已簽名的預共享金鑰 SPK-B、一次性預共享金鑰 OPK-B;
  • 3)Alice 開始使用 DH 協議計算協商金鑰,要引入引數包括:自己建立的兩個金鑰對的私鑰,以及 Bob 的三個公鑰。然後用類似排列組合的方式,將自己的私鑰與對方的公鑰分別帶入 DH 演算法計算。

DH1 = DH(IPK-A, SPK-B)

DH2 = DH(EPK-A, IPK-B)

DH3 = DH(EPK-A, SPK-B)

DH4 = DH(IPK-A, OPK-B)

如圖所示:

然後將計算得到的四個值,前後連線起來,就得到了初始金鑰,如下:

DH = DH1 || DH2 || DH3 || DH4

注:“||”代表連線符,比如 456 || 123 = 456123

但是 DH 這個金鑰太長,不適合作為訊息金鑰,所以對這個初始金鑰進行一次 KDF 計算,以衍生出固定長度的訊息金鑰 S:

S = KDF(DH1 || DH2 || DH3 || DH4)

這一步,Alice 終於計算出了訊息金鑰 S。

於是:

  • 1)Alice 使用訊息金鑰 S 對訊息進行加密,連同自己的身份公鑰 IPK-A 和臨時公鑰 EPK-A 一同發給 Bob;
  • 2)Bob 收到 Alice 的資訊後,取出 Alice 的 2 個公鑰,連同自己的金鑰,使用與 Alice 相同的演算法計算訊息金鑰 S;
  • 3)Bob 和 Alice 使用訊息金鑰進行加密通訊。

由上可知:X3DH 實際是複雜版的 DH 協議。

至此:我們簡單介紹了 Signal Protocol 中最為核心的 X3DH 協議與雙棘輪演算法,基本上可以滿足前向安全和後向安全。當然,真實的處理過程會更為複雜和安全。

7、IM群聊的端到端加密方案

在即時通訊場景中,除了二人之間的聊天以外,還有一個重要的場景就是群聊,那麼群聊時的多人訊息如何做端到端加密呢?

我們再次回到 DH 金鑰協商演算法上的推導過程:顯然,多方情況下依然可以繼續使用 DH 金鑰協商演算法,這就是群聊中端到端加密的基礎。

而 Signal Protocol 在群組聊天中的設計與二人聊天又有所不同,由於群聊的保密性要求相對低一些,只採用了 KDF 鏈棘輪+公鑰簽名來進行加密通訊以保障加密的前向安全。

群組聊天的加解密通訊流程如下:

  • 1)每個群組成員都要首先生成隨機 32 位元組的 KDF 鏈金鑰(Chain Key),用於生成訊息金鑰,以保障訊息金鑰的前向安全性,同時還要生成一個隨機 Curve25519 簽名金鑰對,用於訊息簽名;
  • 2)每個群組成員用向其它成員單獨加密傳送鏈金鑰(Chain Key)和簽名公鑰。此時每一個成員都擁有群內所有成員的鏈金鑰和簽名公鑰;
  • 3)當一名成員傳送訊息時,首先用 KDF 鏈棘輪演算法生成的訊息金鑰加密訊息,然後使用私鑰簽名,再將訊息發給伺服器,由伺服器傳送給其它成員;
  • 4)其它成員收到加密訊息後,首先使用傳送人的簽名公鑰驗證,驗證成功後,使用相應的鏈金鑰生成訊息金鑰,並用訊息金鑰解密;
  • 5)當群組成員離開時,所有的群組成員都清除自己鏈金鑰和簽名公鑰並重新生成,再次單獨發給每一位成員。這樣操作,離開的成員就無法檢視群組內的訊息了。

由上可知:一個人在不同的群組裡,會生成不同的鏈金鑰和簽名金鑰對,以保障群組之間的隔離。在每個群組中,每個成員還要儲存其它成員的 KDF 鏈和簽名公鑰,如果群組成員過多,加解密運算量非常大,會影響傳送和接收速度,同時金鑰管理資料庫也會非常大,讀取效率也會降低。

所以:群組聊天使用 Signal Protocol 協議,群人數不宜太多。

8、端到端加密方案的補充說明

上面我們介紹了即時通訊中二人聊天和群組聊天的端到端加密全部過程。但是正常情況下端到端訊息加密只是加密訊息的實際負載部分(即只加密訊息“體”部分),而訊息的控制層則不會被加密,因為訊息轉發伺服器需要根據控制資訊進行訊息轉發或路由(否則肯定大大影響IM底層的路由和通訊效率,因為需要反覆加密解密)。

為了防止訊息被定向分析(分析使用者什麼時間向誰傳送了訊息,或接收了誰的訊息),我們依然需要對整體即時通訊的長連線鏈路進行加密保護(這說的就是上篇文章裡的通訊連線層加密技術了),防止資訊被中間網路裝置截獲並分析。而且為了防止金鑰伺服器被中間人攻擊,也需要開啟鏈路加密保護。

9、參考資料

[1] 移動端安全通訊的利器——端到端加密(E2EE)技術詳解

[2] 簡述實時音視訊聊天中端到端加密(E2EE)的工作原理

[3] HASH、MAC、HMAC學習

[4] 一文了解加解密、雜湊函式、MAC、數字簽名、證書、CA等

[5] 雙棘輪演算法:端對端加密安全協議,原理以及流程詳解

[6] Signal protocol 開源協議理解

[7] X25519(Curve25519)橢圓曲線參考資料

學習交流:

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

- 開源IM框架原始碼:http://github.com/JackJiang2011/MobileIMSDK備用地址點此

本文已同步釋出於:http://www.52im.net/thread-4026-1-1.html

「其他文章」