融雲網際網路通訊安全系列之端到端加密技術

融雲RongCloud發表於2022-04-26

網際網路通訊技術的發展應用,讓資訊無遠弗屆,連線隨時發生。關注【融雲全球網際網路通訊雲】瞭解更多

但與此同時,網際網路通訊也暴露出了一系列安全問題。我們需要有針對性地應用相關安全技術,增強通訊系統的安全性和可靠性,為保護使用者個人資訊保安築起防護牆。

在融雲網際網路通訊安全系列文章首篇“鏈路安全”中,我們介紹了關於鏈路加密的相關技術和實踐。在傳輸即時通訊訊息時啟用 TLS 鏈路加密,保證訊息在到達伺服器前無法被竊聽和篡改;使用 CA 認證機制,杜絕中間人攻擊。

這些都是提升客戶端到伺服器之間資料傳輸安全性的有效辦法,但是未能解決使用者間通訊隱私性和安全性面臨的風險問題。因為在將資料傳輸到伺服器之後,所有有權訪問此伺服器的人,包括員工、相關供應商及其他有關人員甚至黑客,都有可能讀取到使用者的資料。

因此,端到端加密技術被大力推動,其出現後很快被大眾所認知並接受。在 WhatsApp、Signal、Telegram 等即時通訊軟體中都有所應用。

今天,我們分享網際網路通訊安全系列文章的第三篇,帶領大家一文讀懂端到端訊息加密。

端到端加密方案設計思路

說到端到端加密,我們首先想到的解決方案是,在傳送端傳送訊息前對整個訊息進行加密,接收端接收到訊息後進行解密。這樣,訊息中轉伺服器就無法獲取我們的訊息內容了。

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

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

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

非對稱加密傳輸對稱加密金鑰的演算法,一般歸結兩種方式:一種是以 RSA、ECC 等為主的,公鑰加密私鑰解密的方式,本質是加解密的演算法;另一種是以 DH、ECDH 為主的生成共享金鑰的方式,本質是通過計算協商一個共同的金鑰而不是加解密演算法。

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


此處為了方便理解,附上 DH 演算法介紹:

基於原根的定義及性質,可以定義 Diffie-Hellman 金鑰交換演算法。該演算法描述如下:

  1. 有兩個全域性公開的引數,一個素數 q 和一個整數 a, a 是 q 的一個原根。
  1. 假設使用者 A 和 B 希望交換一個金鑰,使用者 A 選擇一個作為私有金鑰的隨機數 XA(XA<q)< span="">,並計算公開金鑰 YA=a^XA mod q。A 對 XA 的值保密存放而使 YA 能被 B 公開獲得。類似地,使用者 B 選擇一個私有的隨機數 XB<q< span="">,並計算公開金鑰 YB=a^XB mod q。B 對 XB 的值保密存放而使 YB 能被 A 公開獲得。
  1. 使用者 A 產生共享祕密金鑰的計算方式是 K = (YB)^XA mod q。同樣,使用者 B 產生共享祕密金鑰的計算是 K = (YA)^XB mod q。這兩個計算產生相同的結果。

推導過程如下:

K = (YB)^XA mod q

= (a^XB mod q)^XA mod q

= (a^XB)^XA mod q (根據取模運算規則得到)

= a^(XBXA) mod q

= (a^XA)^XB mod q

= (a^XA mod q)^XB mod q

= (YA)^XB mod q

因此相當於雙方已經交換了一個相同的祕密金鑰。

4. 因為 XA 和 XB 是保密的,一個敵對方可以利用的引數只有 q,a,YA 和 YB。因而敵對方被迫取離散對數來確定金鑰。例如,要獲取使用者 B 的祕密金鑰,敵對方必須先計算 XB,然後再使用使用者 B 採用的同樣方法計算其祕密金鑰 K。

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

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

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


總結而言,端到端加密採用生成共享金鑰的方式來傳輸會話金鑰有如下幾個原因:

  1. 如果採用 RSA、ECC 等公鑰加密私鑰解密的方式傳輸金鑰,需要在建立會話時生成臨時金鑰,並通過對方公鑰加密後傳輸到接收端。這就需要完全保證訊息的可靠性,如果該訊息在任何一個環節中丟失或損壞,則後續通訊都無法進行。或者,需要採用更為可靠的傳輸方案,通常做法為需要接收端線上,通過各種確認來保證這個可靠性。而採用共享金鑰的方式則只需要知道對方的公鑰,就可以完成生成共享金鑰,並不一定需要對方線上。
  1. 如果已經生成的臨時對稱金鑰丟失,則需要重新協商金鑰。而採用共享金鑰的方式則只需要知道對方的公鑰,就可以完成生成共享金鑰,不需要重新協商。
  1. 採用公鑰加密私鑰解密的方式至少會比生成共享金鑰方式多一次交換對稱金鑰的通訊過程。
  1. 金鑰協商方式,不僅僅可以完成兩個點之間的金鑰協商,還可以延展到多人之間的共同協商出相同的金鑰,這樣能滿足多人群體溝通的需求。

端到端加密初步方案

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

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

(端到端訊息加密過程)
(端到端訊息加密過程)

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

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

端到端加密方案優化

HMAC

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

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

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

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

ECDH

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

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

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

前向安全

在訊息傳輸過程中,如果協商好的金鑰洩露了,就意味著所有資訊都將暴露於風險之下。為了防止這種情況發生,我們需要每次加密使用的金鑰都與上一次不同,且不可以反向推導得出之前的金鑰。

此處引入一個 Hash 演算法,這個 Hash 演算法可以通過輸入一個金鑰匯出另外一個離散性更大的金鑰,每次傳送訊息時都是用上次的訊息金鑰進行 Hash 運算得出本次金鑰,由於 Hash 演算法具有單向不可逆的特性,因此就無法通過本次的金鑰推導之前的金鑰。從感觀上,這就像一個棘輪,棘輪就是一種特殊的齒輪,他只能往一個方向轉下去,而不能往回轉。

雙棘輪

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

X3DH

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

但是,由於長期金鑰無法被更換,所以方案依然存在著安全隱患。因此,Signal protocol 設計了一種更為複雜和安全的 DH 金鑰交換過程,稱之為 X3DH,即 DH 協議的 3 倍擴充套件版。

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

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

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

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

  1. Alice 要建立一個臨時金鑰對(ephemeral key),我們設成 EPK-A,此金鑰對是為了後面棘輪演算法準備,在此處作用不大;
  1. Alice 從伺服器獲取 Bob 的三種金鑰對的公鑰:身份金鑰對IPK-B;已簽名的預共享金鑰 SPK-B;一次性預共享金鑰 OPK-B;
  1. 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。
  1. Bob 收到 Alice 的資訊後,取出 Alice 的 2 個公鑰,連同自己的金鑰,使用與 Alice 相同的演算法計算訊息金鑰 S。
  1. Bob 和 Alice 使用訊息金鑰進行加密通訊。

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

群聊的端到端加密方案

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

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

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

通訊流程如下:

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

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

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

端到端加密方案補充說明

上面我們介紹了即時通訊中二人聊天和群組聊天的端到端加密全部過程。但是正常情況下端到端訊息加密只是加密訊息的實際負載部分,而訊息的控制層則不會被加密,因為訊息轉發伺服器需要根據控制資訊進行訊息轉發或路由。

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

相關文章