天天看點

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

随着近些年網絡安全事情的頻繁發生,使得使用者對網絡通信安全的意識越來越強。國内外的網絡服務提供商都逐漸提供全站的安全通信服務,如國内的淘寶、百度先後宣布已經完成了全站部署 https。微信現有的安全通信協定是基于使用者登入的時候派發的 SessionKey 對應用資料進行加密的,該協定在工程實作上,已經過多次疊代優化,但是仍然有一些缺點:

1、原有的加密通信協定是存在于業務層的。加密保護的是請求包包體部分,但是標頭部分是明文,標頭包含使用者 id 和請求的業務 id 等資訊,這主要是為了在 proxy 做路由所需要的。這樣會存在資料被截獲後建立映射關聯分析的風險。 

2、原有的加密通信協定使用的密碼學協定和算法與業界最新成果有差距,安全強度有待加強。 

鑒于上述原因,微信需要一套能夠加密保護 Client 到 Server 之間所有網絡通信資料、且加密通信保護必須對業務開發人員透明的安全通信協定,由此誕生了 mmtls。

考慮到系統安全性與可用性和性能等名額之間可能存在互相影響,某種程度上,安全性與這些名額是負相關的。是以在設計的時候對 mmtls 提出了以下要求: 

安全性。主要展現在防竊聽,防篡改,防重放,防僞造。 

低延遲、低資源消耗。要保證資料在傳輸過程中不會增加明顯的延遲;背景負載增加在可接受範圍。

可用性。在一些極端情況下(如 server 負載過高),背景能夠控制提供降級服務,但是要注意不能被攻擊者利用,進行降級攻擊。

可擴充性。協定要可擴充、可更新,友善添加安全強度更高的密碼學元件,友善禁止過時的密碼學元件。

通過分析一些業界公開的安全通信協定發現,它們都不能完全滿足我們的要求,例如 TLS1.2 中每次建立一個安全連接配接都需要額外的 2~1 個 RTT(全握手需要 2-RTT),對于微信這麼一個需要頻繁網絡通信的 IM 軟體來說,這對使用者體驗的傷害是極大的,尤其是在大量短連接配接存在的情況下,額外的 RTT 對使用者延遲的影響是相當明顯的。好在 TLS1.3 草案标準中提出了 0-RTT(不額外增加網絡延時)建立安全連接配接的方法,另外 TLS 協定本身通過版本号、CipherSuite、Extension 機制提供了良好的可擴充性。但是,TLS1.3 草案标準仍然在制定過程中,基于标準的實作更是遙遙無期,并且 TLS1.3 是一個對所有軟體制定的一個通用協定,如果結合微信自己的特點,還有很大的優化空間。是以我們最終選擇基于 TLS1.3 草案标準,設計實作我們自己的安全通信協定 mmtls。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

業務層資料加上 mmtls 之後,由 mmtls 提供安全性,保護業務資料,這類似于 http 加上 tls 後,變成 https,由 tls 保護 http 資料。mmtls 處于業務層和原有的網絡連接配接層之間,不影響原有的網絡政策。對于微信來說這裡的網絡連接配接層就是微信長連接配接(私有協定)和微信短連接配接(http 協定),都是基于 TCP 的。

圖 1 描述的是把 mmtls 看成一個整體,它所處的位置。進入 mmtls 内部,它包含三個子協定:Record 協定、Handshake 協定、Alert 協定。這其實是和 TLS 類似的。它們之間的關系如下圖:

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

Handshake 和 Alert 協定是 Record 協定的上層協定,業務層協定(Application Protocol)也是 record 協定的上層協定,在 Record 協定包中有一個字段,用來區分目前這個 Record 資料包的上層協定是 Handshake、Alert 還是業務協定資料包。Handshake 協定用于完成 Client 與 Server 的握手協商,協商出一個對稱加密密鑰 Key 以及其他密碼材料,用于後續資料加密。Alert 協定用于通知對端發生錯誤,希望對端關閉連接配接,目前 mmtls 為了避免 server 存在過多 TCP Time-Wait 狀态,Alert 消息隻會 server 發送給 client,由 client 主動關閉連接配接。

說明:在下文中,會出現多個對稱密鑰和多個非對稱密鑰對,在本文中我會給有些密鑰取一個專有的名字,以友善了解避免混淆,如:<code>pre_master_key,pre_shared_key,cli_pub_key,cli_pri_key</code> 等,凡是類似 <code>xxx_pub_key、xxx_pri_key</code> 這種名字的都是一對非對稱公私鑰,<code>sign_key</code> 和 <code>verify_key</code> 是一對簽名/驗簽的密鑰,其他的以 key 結尾的 <code>xxx_key</code> 都是對稱密鑰。

Handshake 協定其實做的最主要的事情就是完成加密密鑰的協商,即讓通信雙方安全地獲得一緻的對稱密鑰,以進行加密資料傳輸。在此基礎上,還完成了一些優化工作,如複用 session 以減少握手時間。

在這裡說明一下,為什麼 mmtls 以及 TLS 協定需要一個 Handshake 子協定和 Record 子協定?其實"認證密鑰協商 對稱加密傳輸"這種混合加密結構,是絕大多數加密通信協定的通用結構,在 mmtls/TLS 中 Handshake 子協定負責密鑰協商, Record 子協定負責資料對稱加密傳輸。造成這種混合加密結構的本質原因還是因為單獨使用公鑰加密元件或對稱加密元件都有不可避免的缺點。公鑰加密元件計算效率往往遠低于對稱加密元件,直接使用公鑰加密元件加密業務資料,這樣的性能損耗任何 Server 都是無法承受的;而如果單獨使用對稱加密元件進行網絡加密通信,在 Internet 這種不安全的信道下,這個對稱加密密鑰如何擷取往往是一個難以解決的問題,是以結合兩類密碼元件的優勢,産生了"認證密鑰協商 對稱加密傳輸"這種混合加密結構。另外,mmtls/TLS 這種安全性和擴充性都很強的安全通信協定,在解決實際安全通信問題的時候,會有非常多的細節問題,是以分離出兩個子協定來隔離複雜性。

根據 TLS1.3 的描述,實際上有 2 種 1-RTT 的密鑰協商方式(1-RTT ECDHE、 1-RTT PSK)和 4 種 0-RTT 的密鑰協商方式(0-RTT PSK, 0-RTT ECDH, 0-RTT PSK-ECDHE, 0-RTT ECDH-ECDHE),mmtls 結合微信的特點,在保證安全性和性能的前提下,隻保留了三種密鑰協商方式(1-RTT ECDHE, 1-RTT PSK, 0-RTT PSK),并做了一些優化,後面會詳細分析如何産生這種決策的。

1.ECDH 密鑰協商

首先看一個,會遭受到攻擊的密鑰協商過程。通信雙方 Alice 和 Bob 使用 ECDH 密鑰交換協定進行密鑰協商,ECDH 密鑰交換協定擁有兩個算法:

密鑰生成算法 <code>ECDH_Generate_Key</code>,輸出一個公鑰和私鑰對 <code>(ECDH_pub_key, ECDH_pri_key)</code>,<code>ECDH_pri_key</code> 需要秘密地儲存,<code>ECDH_pub_key</code> 可以公開發送給對方。

密鑰協商算法 <code>ECDH_compute_key</code>,以對方的公鑰和自己的私鑰作為輸入,計算出一個密鑰 Key,<code>ECDH_compute_key</code> 算法使得通信雙方計算出的密鑰 Key 是一緻的。

這樣一來 Alice 和 Bob 僅僅通過交換自己的公鑰 <code>ECDH_pub_key</code>,就可以在 Internet 這種公開信道上共享一個相同密鑰 Key,然後用這個 Key 作為對稱加密算法的密鑰,進行加密通信。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

但是這種密鑰協商算法仍然存在一個問題。當 Bob 将他的 <code>Bob_ECDH_pub_key</code> 發送給 Alice 時,攻擊者可以截獲 <code>Bob_ECDH_pub_key</code>,自己運作 <code>ECDH_Generate_Key 算法</code> 産生一個公鑰/私鑰對,然後把他産生的公鑰發送給 Alice。同理,攻擊者可以截獲 Alice 發送給 Bob 的 <code>Alice_ECDH_pub_key</code>,再運作 <code>ECDH_Generate_Key 算法</code> 産生一個公鑰/私鑰對,并把這個公鑰發送給 Bob。Alice 和 Bob 仍然可以執行協定,産生一個密鑰 Key。但實際上,Alice 産生的密鑰 Key 實際上是和攻擊者 Eve 協商的;Bob 産生的密鑰 Key 也是和攻擊者協商 Eve 的。這種攻擊方法被稱為中間人攻擊(Man In The Middle Attack)。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

那麼,有什麼解決辦法中間人攻擊呢?産生中間人攻擊的本質原因是協商過程中的資料沒有經過端點認證,通信兩端不知道收到的協商資料是來自對端還是來自中間人,是以單純的"密鑰協商"是不夠的,還需要"帶認證的密鑰協商"。對資料進行認證其實有對稱和非對稱的兩種方式:基于消息認證碼(Message Authentication Code)的對稱認證和基于簽名算法的非對稱認證。消息認證碼的認證方式需要一個私密的 Key,由于此時沒有一個私密的 Key,是以 ECDH 認證密鑰協商就是 ECDH 密鑰協商加上數字簽名算法。在 mmtls 中我們采用的數字簽名算法為 ECDSA。

雙方密鑰協商時,再分别運作簽名算法對自己發出的公鑰 <code>ECDH_pub_key</code> 進行簽名。收到資訊後,首先驗證簽名,如果簽名正确,則繼續進行密鑰協商。注意到,由于簽名算法中的公鑰 <code>ECDSA_verify_key</code> 是一直公開的,攻擊者沒有辦法阻止别人擷取公鑰,除非完全掐斷發送方的通信。這樣一來,中間人攻擊就不存在了,因為 Eve 無法僞造簽名。具體過程如圖 5 所示:

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

事實上,在實際通信過程中,隻需要通信中某一方簽名它的協商資料就可以保證不被中間人攻擊,mmtls 就是隻對 Server 做認證,不對 Client 做認證,因為微信用戶端釋出出去後,任何人都可以獲得,隻要能夠保證用戶端程式本身的完整性,就相當于保證了用戶端程式是由官方釋出的,為認證合法的用戶端,而用戶端程式本身的完整性不是 mmtls 協定保護的範疇。在這一點上,TLS 要複雜一些,TLS 作為一個通用的安全通信協定,可能會存在一些需要對 Client 進行認證的場合,是以 TLS 提供了可選的雙方互相認證的能力,通過握手協商過程中選擇的 CipherSuite 是什麼類型來決定是否要對 Server 進行認證,通過 Server 是否發送 CertificateRequest 握手消息來決定是否要對 Client 進行認證。由于 mmtls 不需要對 Client 做認證,在這塊内容上比 TLS 簡潔許多,更加輕量級。

2.PSK 密鑰協商

PSK 是在一次 ECDH 握手中由 server 下發的内容,它的大緻資料結構為 <code>PSK{key,ticket{key}}</code>,即 PSK 包含一個用來做對稱加密密鑰的 key 明文,以及用 <code>ticket_key</code> 對 <code>key</code> 進行加密的密文 <code>ticket</code>,當然 PSK 是在安全信道中下發的,也就是說在網絡中進行傳輸的時候 PSK 是加密的,中間人是拿不到 <code>key</code> 的。其中 <code>ticket_key</code> 隻有 server 才知道,由 server 負責私密儲存。

PSK 協商比較簡單,Client 将 PSK 的 <code>ticket{key}</code> 部分發送給 Server,由于隻有 Server 才知道 <code>ticket_key</code>,是以 key 是不會被竊聽的。Server 拿到 ticket 後,使用 <code>ticket_key</code> 解密得到 key,然後 Server 用基于協商得到的密鑰 key,對協商資料計算消息認證碼來認證,這樣就完成了 PSK 認證密鑰協商。PSK 認證密鑰協商使用的都是對稱算法,性能上比 ECDH 認證密鑰協商要好很多。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

上述的兩種認證密鑰協商方式(1-RTT ECDHE, 1-RTT PSK)都需要一個額外 RTT 去擷取對稱加密 key,在這個協商的 RTT 中是不帶有業務資料的,全部都是協商資料。那麼是否存在一種密鑰協商方式是在握手協商的過程中就安全地将業務資料傳遞給對端呢?答案是有的,TLS1.3 草案中提到了 0-RTT 密鑰協商的方法。

1. 0-RTT ECDH 密鑰協商

0-RTT 握手想要達到的目标是在握手的過程中,捎帶業務資料到對端,這裡難點是如何在用戶端發起協商請求的時候就生成一個可信的對稱密鑰加密業務資料。在 1-RTT ECDHE 中,Client 生成一對公私鑰 <code>(cli_pub_key, cli_pri_key)</code>,然後将公鑰 <code>cli_pub_key</code> 傳遞給 Server,然後 Server 生成一對公私鑰 <code>(svr_pub_key, svr_pri_key)</code> 并将公鑰 <code>svr_pub_key</code> 傳遞給 Client,Client 收到 <code>svr_pub_key</code> 後才能計算出對稱密鑰。上述過程 <code>(svr_pub_key, svr_pri_key)</code> 由于是臨時生成的,需要一個 RTT 将 <code>svr_pub_key</code> 傳遞給用戶端,如果我們能夠預先生成一對公私鑰 <code>(static_svr_pub_key, static_svr_pri_key)</code> 并将 <code>static_svr_pub_key</code> 預置在 Client 中,那麼 Client 可以在發起握手前就通過 <code>static_svr_pub_ke</code> 和 <code>cli_pub_key</code> 生成一個對稱密鑰 <code>SS(Static Secret)</code>,然後用 SS 加密第一個業務資料包(實際上是通過 SS 衍生的密鑰對業務資料進行加密,後面詳述),這樣将 SS 加密的業務資料包和 <code>cli_pub_key</code> 一起傳給 Server,Server 通過 <code>cli_pub_key</code> 和 <code>static_server_private_key</code> 算出 SS,解密業務資料包,這樣就達到了 0-RTT 密鑰協商的效果。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

這裡說明一下:ECDH 協商中,如果公私鑰對都是臨時生成的,一般稱為 ECDHE,是以 1-RTT 的 ECDH 協商方式被稱為 1-RTT ECDHE 握手,0-RTT 中有一個靜态内置的公鑰,是以稱為 0-RTT ECDH 握手。

2. 0-RTT PSK 密鑰協商

0-RTT PSK 握手比較簡單,回顧 1-RTT PSK 握手,其實在進行 1-RTT PSK 握手之前,Client 已經有一個對稱加密密鑰 key 了,就直接拿這個對稱加密密鑰 key 加密業務資料,然後将其和握手協商資料 <code>ticket{key}</code> 一起傳遞給 Server 就可以了。

3.提高 0-RTT 密鑰協商的安全性

PFS(perfect forward secrecy),中文可叫做完全前向保密。它要求一個密鑰被破解,并不影響其他密鑰的安全性,反映的密鑰協商過程中,大緻的意思是用來産生會話密鑰的長期密鑰洩露出去,不會造成之前通信時使用的會話密鑰的洩露;或者密鑰協商方案中不存在長期密鑰,所有協商材料都是臨時生成的。

上面所述的 0-RTT ECDH 密鑰協商加密的資料的安全性依賴于長期儲存的密鑰 <code>static_svr_pri_key</code>,如果 <code>static_svr_pri_key</code> 洩露,那麼所有基于 0-RTT ECDH 協商的密鑰 SS 都将被輕松計算出來,它所加密的資料也沒有任何保密性可言,為了提高前向安全性,我們在進行 0-RTT ECDH 協商的過程中也進行 ECDHE 協商,這種協商方式稱為 0-RTT ECDH-ECDHE 密鑰協商。如下圖所示:

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

這樣,我們基于 <code>static_svr_pri_key</code> 保護的資料就隻有第一個業務資料包 AppData,後續的包都是基于 ES(Ephemeral Secret) 對業務資料進行保護的。這樣即使 <code>static_svr_pri_key</code> 洩露,也隻有連接配接的第一個業務資料包能夠被解密,提高前向安全性。

同樣的,0-RTT PSK 密鑰協商加密的資料的安全性依賴于長期儲存密鑰 <code>ticket_key</code>,如果 <code>ticket_key</code> 洩露,那麼所有基于 <code>ticket_key</code> 進行保護的資料都将失去保密性,是以同樣可以在 0-RTT PSK 密鑰協商的過程中,同時完成 ECDHE 密鑰協商,提高前向安全性。

根據前面的描述可以知道,要使得密鑰協商過程不被中間人攻擊,就必須要對協商資料進行認證。下面拿 1-RTT ECDHE 握手方式來說明在進行認證過程中需要注意的細節。在 1-RTT ECDHE 中的認證方式是使用 ECDSA 簽名算法的非對稱認證方式,整個過程大緻如下:Server 在收到用戶端的 <code>cli_pub_key</code> 後,随機生成一對 ECDH 公私鑰 <code>(svr_pub_key, svr_pri_key)</code>,然後用簽名密鑰 <code>sign_key</code> 對 <code>svr_pub_key</code> 進行簽名,得到簽名值 Signature,并把簽名值 Signature 和 <code>svr_pub_key</code> 一起發送給用戶端。用戶端收到之後,用 <code>verify_key</code> 進行驗簽(<code>verify_key</code> 和 <code>sign_key</code> 是一對 ECDSA 密鑰),驗簽成功後才會繼續走協商對稱密鑰的流程。

上面的認證過程,有三個值得關注的點:

Verify_Key 如何下發給用戶端?這實際上是公鑰派發的問題,TLS 是使用證書鍊的方式來派發公鑰(證書),對于微信來說,如果使用證書鍊的方式來派發 Server 的公鑰(證書),無論自建 Root CA 還是從 CA 處申請證書,都會增加成本且在驗簽過程中會存在額外的資源消耗。由于用戶端是由我們自己釋出的,我們可以将 <code>verify_key</code> 直接内置在用戶端,這樣就避免證書鍊驗證帶來的時間消耗以及證書鍊傳輸帶來的帶寬消耗。

如何避免簽名密鑰 <code>sign_key</code> 洩露帶來的影響?

如果 <code>sign_key</code> 洩露,那麼任何人都可以僞造成 Server 欺騙 Client,因為它拿到了 <code>sign_key</code>,它就可以簽發任何内容,Client 用 <code>verify_key</code> 去驗證簽名必然驗簽成功。是以 <code>sign_key</code> 如果洩露必須要能夠對 <code>verify_key</code> 進行撤銷,重新派發新的公鑰。這其實和前一問題是緊密聯系的,前一問題是公鑰派發問題,本問題是公鑰撤銷問題。TLS 是通過 CRL 和 OCSP 兩種方式來撤銷公鑰的,但是這兩種方式存在撤銷不及時或給驗證帶來額外延遲的副作用。由于 mmtls 是通過内置·verify_key·在用戶端,必要時通過強制更新用戶端的方式就能完成公鑰撤銷及更新。另外,<code>sign_key</code> 是需要 Server 高度保密的,一般不會被洩露,對于微信背景來說,類似于 <code>sign_key</code> 這樣,需要長期私密儲存的密鑰在之前也有存在,早已形成了一套方法和流程來應對長期私密儲存密鑰的問題。

用 <code>sign_key</code> 進行簽名的内容僅僅隻包含 <code>svr_pub_key</code> 是否有隐患?

回顧一下,上面描述的帶認證的 ECDH 協商過程,似乎已經足夠安全,無懈可擊了,但是,面對成億的用戶端發起 ECDH 握手到成千上萬台接入層機器,每台機器對一個 TCP 連接配接随機生成不同的 ECDH 公私鑰對,這裡試想一種情況,假設某一台機器某一次生成的 ECDH 私鑰 <code>svr_pri_key1</code> 洩露,這實際上是可能的,因為臨時生成的 ECDH 公私鑰對本身沒有做任何保密儲存的措施,是明文、短暫地存放在記憶體中,一般情況沒有問題,但在分布式環境,大量機器大量随機生成公私鑰對的情況下,難保某一次不被洩露。這樣用 <code>sign_key</code>(<code>sign_key</code> 是長期儲存,且分布式環境共享的)對 <code>svr_pub_key1</code> 進行簽名得到簽名值 Signature1,此時攻擊者已經拿到 <code>svr_pri_key1,svr_pub_key1 和 Signature1</code>,這樣他就可以實施中間人攻擊,讓用戶端每次拿到的伺服器 ECDH 公鑰都是 <code>svr_pub_key1</code>:用戶端随機生成 ECDH 公私鑰對(<code>cli_pub_key, cli_pri_key)</code> 并将 <code>cli_pub_key</code> 發給 Server,中間人将消息攔截下來,将 <code>client_pub_key</code> 替換成自己生成的 <code>client_pub_key'</code>,并将 <code>svr_pub_key1</code> 和 Signature1 回給 Client,這樣 Client 就通過計算 <code>ECDH_Compute_Key(svr_pub_key1, cli_pri_key)=Key1</code>, Server 通過計算 <code>ECDH_Compute_Key(client_pub_key', svr_pub_key)=Key'</code>,中間人既可以計算出 Key1 和 Key',這樣它就可以用 Key1 和 Client 通信,用 Key'和 Server 進行通信。

發生上述被攻擊的原因在于一次握手中公鑰的簽名值被用于另外一次握手中,如果有一種方法能夠使得這個簽名值和一次握手一一對應,那麼就能解決這個問題。解決辦法也很簡單,就是在握手請求的 ClientHello 消息中帶一個 <code>Client_Random</code> 随機值,然後在簽名的時候将 <code>Client_Random</code> 和 <code>svr_pub_key</code> 一起做簽名,這樣得到的簽名值就與 <code>Client_Random</code> 對應了。mmtls 在實際處理過程中,為了避免 Client 的随機數生成器有問題,造成生成不夠随機的 Client_Random,實際上 Server 也會生成一個随機數 <code>Server_Random</code>,然後在對公鑰簽名的時候将 <code>Client_Random、Server_Random、svr_pub_key</code> 一起做簽名,這樣由 <code>Client_Random、Server_Random</code> 保證得到的簽名值唯一對應一次握手。

上面一共介紹了 2 種 1-RTT 密鑰協商方式和 4 種 0-RTT 密鑰協商方式。

基于 TLS 1.3的微信安全通信協定 mmtls 介紹(上)

PSK 握手全程無非對稱運算,Server 性能消耗小,但前向安全性弱,ECDHE 握手有非對稱運算,Server 性能消耗大,但前向安全性相對更強,那麼如何結合兩者優勢進行密鑰協商方式的選擇呢?

首先 PSK 是如何獲得的呢?PSK 是在一次成功的 ECDH(E) 握手中下發的(在上面的圖 7、圖 8 沒有畫出下發 PSK 的部分),如果用戶端沒有 PSK,那麼顯然是隻能進行 ECDH(E) 握手了。由于 PSK 握手和 ECDH(E) 握手的巨大性能差異,那麼在 Client 有 PSK 的情況下,應該進行 PSK 握手。那麼在沒有 PSK 的情況下,上面的 1-RTT ECDHE、0-RTT ECDH、0-RTT ECDH-ECDHE 具體應該選擇哪一種呢?在有 PSK 的情況下,應該選擇 1-RTT PSK、0-RTT PSK 還是 0-RTT PSK-ECDHE 呢?

對于握手方式的選擇,我們也是幾經過修改,最後結合微信網絡連接配接的特點,我們選擇了 1-RTT ECDHE 握手、1-RTT PSK 握手、0-RTT PSK 握手。微信目前有兩個資料傳輸通道:1.基于 HTTP 協定的短連接配接 2.基于私有協定的長連接配接。

微信長連接配接有一個特點,就是在建立好 TCP 連接配接之後,會在此 TCP 連接配接上先發一個長連 nooping 包,目的是驗證長連接配接的連通性(由于長連接配接是私有協定,部分中間路由會過濾掉這種私有協定的資料包),這就是說長連接配接在建立時的第一個資料包是不會發送業務資料的,是以使用 1-RTT 的握手方式,由第一個握手包取代之前的 nooping 包去探測長連的連通性,這樣并不會增加長連的網絡延時,是以我們選取在長連接配接情況下,使用 1-RTT ECDHE 和 1-RTT PSK 這兩種密鑰協商方式。

微信短連接配接為了相容老版本的 HTTP 協定,整個通信過程就隻有一個 RTT,也就是說 Client 建立 TCP 連接配接後,通過 HTTP POST 一個業務請求包到 Server,Server 回一個 HTTP 響應,Client 處理後立馬斷掉 TCP 連接配接。對于短連接配接,我們應該盡量使用 0-RTT 的握手方式,因為一個短連接配接原來就隻存在一個 RTT,如果我們大量使用 1-RTT 的握手方式,那麼直接導緻短連接配接至少需要 2 個 RTT 才能完成業務資料的傳輸,導緻時延加倍,使用者體驗較差。

這裡存在兩種情況:(1) 用戶端沒有 PSK,為了安全性,這時和長連接配接的握手方式一樣,使用 1-RTT ECDHE;(2) 用戶端有 PSK,這時為了減少網絡時延,應該使用 0-RTT PSK 或 0-RTT PSK-ECDHE,在這兩種握手方式下,由于業務請求包始終是基于 PSK 進行保護的,同一個 PSK 多次協商出來的對稱加密 key 是同一個,這個對稱加密 key 的安全性依賴于 <code>ticket_key</code> 的安全性,是以 0-RTT 情況下,業務請求包始終是無法做到前向安全性。0-RTT PSK-ECDHE 這種方式,隻能保證本短連接配接業務響應回包的前向安全性,這帶來安全性上的優勢是比較小的,但是與 0-RTT PSK 握手方式相比,0-RTT PSK-ECDHE 在每次握手對 server 會多 2 次 ECDH 運算和 1 次 ECDSA 運算。微信的短連接配接是非常頻繁的,這對性能影響極大,是以綜合考慮,在用戶端有 PSK 的情況下,我們選擇使用 0-RTT PSK 握手。由于 0-RTT PSK 握手安全性依賴 <code>ticket_key</code>,為了加強安全性,在實作上,PSK 必須要限制過期時間,避免長期用同一個 PSK 來進行握手協商;<code>ticket_key</code> 必須定期輪換,且具有高度機密的運維級别。

另外,為了提高系統可用性,實際上 mmtls 在一次成功的 ECDH 握手中會下發兩個 PSK,一個生命周期短保證安全性,一個生命周期長保證可用性。在一次 ECDH 握手中,請求會帶上生命周期長的 PSK(如果存在的話),背景可根據負載情況進行權衡,選擇使用 ECDH 握手或者 PSK 握手。