转:基于TLS1.3的微信安全通信协议mmtls介绍
轉自:?https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286266&idx=1&sn=f5d049033e251cccc22e163532355ddf&scene=0&key=b28b03434249256b2a5d4fdf323a185a798eaf972317ca3a47ef060d35c5cd8a4ae35715466d5bb5a558e424d20bef6c&ascene=0&uin=Mjc3OTU3Nzk1&devicetype=iMac+MacBookPro10%2C1+OSX+OSX+10.10.5+build%2814F1713%29&version=11020201&pass_ticket=8lpzOjRJO3IS%2BmKcvsqRN%2FlzlWyR2q2fmKv15GKO2PPYAKDGPXDhyfntueC4bIod
?
編者的話:近年來網絡安全事件層出不窮,確保億萬用戶的安全隱私是我們微信義不容辭的責任。當然,我們更要保證用戶穩定、快速的聊天體驗,所以我們有了mmtls。文章干貨滿滿,建議大家點擊閱讀全文,仔細品味!
?
一、背景
隨著近些年網絡安全事情的頻繁發生,使得用戶對網絡通信安全的意識越來越強。國內外的網絡服務提供商都逐漸提供全站的安全通信服務,如國內的淘寶、百度先后宣布已經完成了全站部署https。微信現有的安全通信協議是基于用戶登錄的時候派發的SessionKey對應用數據進行加密的,該協議在工程實現上,已經過多次迭代優化,但是仍然有一些缺點:
原有的加密通信協議是存在于業務層的。加密保護的是請求包包體部分,但是包頭部分是明文,包頭包含用戶id和請求的業務id等信息,這主要是為了在proxy做路由所需要的。這樣會存在數據被截獲后建立映射關聯分析的風險。
原有的加密通信協議使用的密碼學協議和算法與業界最新成果有差距,安全強度有待加強。
鑒于上述原因,微信需要一套能夠加密保護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。
三、mmtls協議設計
3.1 總體架構
業務層數據加上mmtls之后,由mmtls提供安全性,保護業務數據,這類似于http加上tls后,變成https,由tls保護http數據。mmtls處于業務層和原有的網絡連接層之間,不影響原有的網絡策略。對于微信來說這里的網絡連接層就是微信長連接(私有協議)和微信短連接(http協議),都是基于TCP的。
圖1描述的是把mmtls看成一個整體,它所處的位置。進入mmtls內部,它包含三個子協議:Record協議、Handshake協議、Alert協議。這其實是和TLS類似的。它們之間的關系如下圖:?
Handshake和Alert協議是Record協議的上層協議,業務層協議(Application Protocol)也是record協議的上層協議,在Record協議包中有一個字段,用來區分當前這個Record數據包的上層協議是Handshake、Alert還是業務協議數據包。Handshake協議用于完成Client與Server的握手協商,協商出一個對稱加密密鑰Key以及其他密碼材料,用于后續數據加密。Alert協議用于通知對端發生錯誤,希望對端關閉連接,目前mmtls為了避免server存在過多TCP Time-Wait狀態,Alert消息只會server發送給client,由client主動關閉連接。
說明:在下文中,會出現多個對稱密鑰和多個非對稱密鑰對,在本文中我會給有些密鑰取一個專有的名字,以方便理解避免混淆,如:pre_master_key,pre_shared_key,cli_pub_key,cli_pri_key?等,凡是類似xxx_pub_key、xxx_pri_key這種名字的都是一對非對稱公私鑰,sign_key和verify_key是一對簽名/驗簽的密鑰,其他的以key結尾的xxx_key都是對稱密鑰。
3.2 Handshake協議 --- 安全地協商出對稱加密密鑰
Handshake協議其實做的最主要的事情就是完成加密密鑰的協商,即讓通信雙方安全地獲得一致的對稱密鑰,以進行加密數據傳輸。在此基礎上,還完成了一些優化工作,如復用session以減少握手時間。
在這里說明一下,為什么mmtls以及TLS協議需要一個Handshake子協議和Record子協議?其實“認證密鑰協商+對稱加密傳輸”這種混合加密結構,是絕大多數加密通信協議的通用結構,在mmtls/TLS中Handshake子協議負責密鑰協商, Record子協議負責數據對稱加密傳輸。造成這種混合加密結構的本質原因還是因為單獨使用公鑰加密組件或對稱加密組件都有不可避免的缺點。公鑰加密組件計算效率往往遠低于對稱加密組件,直接使用公鑰加密組件加密業務數據,這樣的性能損耗任何Server都是無法承受的。
而如果單獨使用對稱加密組件進行網絡加密通信,在Internet這種不安全的信道下,這個對稱加密密鑰如何獲取往往是一個難以解決的問題,因此結合兩類密碼組件的優勢,產生了“認證密鑰協商+對稱加密傳輸”這種混合加密結構。另外,mmtls/TLS這種安全性和擴展性都很強的安全通信協議,在解決實際安全通信問題的時候,會有非常多的細節問題,因此分離出兩個子協議來隔離復雜性。
3.2.1 帶認證的密鑰協商
根據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),并做了一些優化,后面會詳細分析如何產生這種決策的。
3.2.1.1 1-RTT密鑰協商
1.ECDH密鑰協商
首先看一個,會遭受到攻擊的密鑰協商過程。通信雙方Alice和Bob使用ECDH密鑰交換協議進行密鑰協商,ECDH密鑰交換協議擁有兩個算法:
-
密鑰生成算法ECDH_Generate_Key,輸出一個公鑰和私鑰對(ECDH_pub_key, ECDH_pri_key),ECDH_pri_key需要秘密地保存,ECDH_pub_key可以公開發送給對方。
-
密鑰協商算法ECDH_compute_key,以對方的公鑰和自己的私鑰作為輸入,計算出一個密鑰Key,ECDH_compute_key算法使得通信雙方計算出的密鑰Key是一致的。
這樣一來Alice和Bob僅僅通過交換自己的公鑰ECDH_pub_key,就可以在Internet這種公開信道上共享一個相同密鑰Key,然后用這個Key作為對稱加密算法的密鑰,進行加密通信。?
但是這種密鑰協商算法仍然存在一個問題。當Bob將他的Bob_ECDH_pub_key發送給Alice時,攻擊者可以截獲Bob_ECDH_pub_key,自己運行ECDH_Generate_Key算法產生一個公鑰/私鑰對,然后把他產生的公鑰發送給Alice。同理,攻擊者可以截獲Alice發送給Bob的Alice_ECDH_pub_key,再運行ECDH_Generate_Key算法產生一個公鑰/私鑰對,并把這個公鑰發送給Bob。Alice和Bob仍然可以執行協議,產生一個密鑰Key。但實際上,Alice產生的密鑰Key實際上是和攻擊者Eve協商的;Bob產生的密鑰Key也是和攻擊者協商Eve的。這種攻擊方法被稱為中間人攻擊(Man In The Middle Attack)。?
那么,有什么解決辦法中間人攻擊呢?產生中間人攻擊的本質原因是協商過程中的數據沒有經過端點認證,通信兩端不知道收到的協商數據是來自對端還是來自中間人,因此單純的“密鑰協商”是不夠的,還需要“帶認證的密鑰協商”。對數據進行認證其實有對稱和非對稱的兩種方式:基于消息認證碼(Message Authentication Code)的對稱認證和基于簽名算法的非對稱認證。消息認證碼的認證方式需要一個私密的Key,由于此時沒有一個私密的Key,因此ECDH認證密鑰協商就是ECDH密鑰協商加上數字簽名算法。在mmtls中我們采用的數字簽名算法為ECDSA。
雙方密鑰協商時,再分別運行簽名算法對自己發出的公鑰ECDH_pub_key進行簽名。收到信息后,首先驗證簽名,如果簽名正確,則繼續進行密鑰協商。注意到,由于簽名算法中的公鑰ECDSA_verify_key是一直公開的,攻擊者沒有辦法阻止別人獲取公鑰,除非完全掐斷發送方的通信。這樣一來,中間人攻擊就不存在了,因為Eve無法偽造簽名。具體過程如圖5所示:??
事實上,在實際通信過程中,只需要通信中某一方簽名它的協商數據就可以保證不被中間人攻擊,mmtls就是只對Server做認證,不對Client做認證,因為微信客戶端發布出去后,任何人都可以獲得,只要能夠保證客戶端程序本身的完整性,就相當于保證了客戶端程序是由官方發布的,為認證合法的客戶端,而客戶端程序本身的完整性不是mmtls協議保護的范疇。
在這一點上,TLS要復雜一些,TLS作為一個通用的安全通信協議,可能會存在一些需要對Client進行認證的場合,因此TLS提供了可選的雙方相互認證的能力,通過握手協商過程中選擇的CipherSuite是什么類型來決定是否要對Server進行認證,通過Server是否發送CertificateRequest握手消息來決定是否要對Client進行認證。由于mmtls不需要對Client做認證,在這塊內容上比TLS簡潔許多,更加輕量級。
2.PSK密鑰協商
PSK是在一次ECDH握手中由server下發的內容,它的大致數據結構為PSK{key,ticket{key}},即PSK包含一個用來做對稱加密密鑰的key明文,以及用ticket_key對key進行加密的密文ticket,當然PSK是在安全信道中下發的,也就是說在網絡中進行傳輸的時候PSK是加密的,中間人是拿不到key的。其中ticket_key只有server才知道,由server負責私密保存。
PSK協商比較簡單,Client將PSK的ticket{key}部分發送給Server,由于只有Server才知道ticket_key,因此key是不會被竊聽的。Server拿到ticket后,使用ticket_key解密得到key,然后Server用基于協商得到的密鑰key,對協商數據計算消息認證碼來認證,這樣就完成了PSK認證密鑰協商。PSK認證密鑰協商使用的都是對稱算法,性能上比ECDH認證密鑰協商要好很多。?
3.2.1.2 0-RTT密鑰協商
上述的兩種認證密鑰協商方式(1-RTT ECDHE, 1-RTT PSK)都需要一個額外RTT去獲取對稱加密key,在這個協商的RTT中是不帶有業務數據的,全部都是協商數據。那么是否存在一種密鑰協商方式是在握手協商的過程中就安全地將業務數據傳遞給對端呢?答案是有的,TLS1.3草案中提到了0-RTT密鑰協商的方法。
1. 0-RTT ECDH密鑰協商
0-RTT 握手想要達到的目標是在握手的過程中,捎帶業務數據到對端,這里難點是如何在客戶端發起協商請求的時候就生成一個可信的對稱密鑰加密業務數據。在1-RTT ECDHE中,Client生成一對公私鑰(cli_pub_key, cli_pri_key),然后將公鑰cli_pub_key傳遞給Server,然后Server生成一對公私鑰(svr_pub_key, svr_pri_key)并將公鑰svr_pub_key傳遞給Client,Client收到svr_pub_key后才能計算出對稱密鑰。
上述過程(svr_pub_key, svr_pri_key)由于是臨時生成的,需要一個RTT將svr_pub_key傳遞給客戶端,如果我們能夠預先生成一對公私鑰(static_svr_pub_key, static_svr_pri_key)并將static_svr_pub_key預置在Client中,那么Client可以在發起握手前就通過static_svr_pub_ke和cli_pub_key生成一個對稱密鑰SS(Static Secret),然后用SS加密第一個業務數據包(實際上是通過SS衍生的密鑰對業務數據進行加密,后面詳述),這樣將SS加密的業務數據包和cli_pub_key一起傳給Server,Server通過cli_pub_key和static_server_private_key算出SS,解密業務數據包,這樣就達到了0-RTT密鑰協商的效果。?
這里說明一下: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加密業務數據,然后將其和握手協商數據ticket{key}一起傳遞給Server就可以了。
3.提高0-RTT密鑰協商的安全性
PFS(perfect forward secrecy),中文可叫做完全前向保密。它要求一個密鑰被破解,并不影響其他密鑰的安全性,反映的密鑰協商過程中,大致的意思是用來產生會話密鑰的長期密鑰泄露出去,不會造成之前通信時使用的會話密鑰的泄露;或者密鑰協商方案中不存在長期密鑰,所有協商材料都是臨時生成的。
上面所述的0-RTT ECDH密鑰協商加密的數據的安全性依賴于長期保存的密鑰static_svr_pri_key,如果static_svr_pri_key泄露,那么所有基于0-RTT ECDH協商的密鑰SS都將被輕松計算出來,它所加密的數據也沒有任何保密性可言,為了提高前向安全性,我們在進行0-RTT ECDH協商的過程中也進行ECDHE協商,這種協商方式稱為0-RTT ECDH-ECDHE密鑰協商。如下圖所示:? 這樣,我們基于static_svr_pri_key保護的數據就只有第一個業務數據包AppData,后續的包都是基于ES(Ephemeral Secret)對業務數據進行保護的。這樣即使static_svr_pri_key泄露,也只有連接的第一個業務數據包能夠被解密,提高前向安全性。
同樣的,0-RTT PSK密鑰協商加密的數據的安全性依賴于長期保存密鑰ticket_key,如果ticket_key泄露,那么所有基于ticket_key進行保護的數據都將失去保密性,因此同樣可以在0-RTT PSK密鑰協商的過程中,同時完成ECDHE密鑰協商,提高前向安全性。
3.2.2 密鑰協商需要關注的細節
根據前面的描述可以知道,要使得密鑰協商過程不被中間人攻擊,就必須要對協商數據進行認證。下面拿1-RTT ECDHE握手方式來說明在進行認證過程中需要注意的細節。在1-RTT ECDHE中的認證方式是使用ECDSA簽名算法的非對稱認證方式,整個過程大致如下:Server在收到客戶端的cli_pub_key后,隨機生成一對ECDH公私鑰(svr_pub_key, svr_pri_key),然后用簽名密鑰sign_key對svr_pub_key進行簽名,得到簽名值Signature,并把簽名值Signature和svr_pub_key一起發送給客戶端。客戶端收到之后,用verify_key進行驗簽(verify_key和sign_key是一對ECDSA密鑰),驗簽成功后才會繼續走協商對稱密鑰的流程。
上面的認證過程,有三個值得關注的點:
-
Verify_Key如何下發給客戶端?
這實際上是公鑰派發的問題,TLS是使用證書鏈的方式來派發公鑰(證書),對于微信來說,如果使用證書鏈的方式來派發Server的公鑰(證書),無論自建Root CA還是從CA處申請證書,都會增加成本且在驗簽過程中會存在額外的資源消耗。由于客戶端是由我們自己發布的,我們可以將verify_key直接內置在客戶端,這樣就避免證書鏈驗證帶來的時間消耗以及證書鏈傳輸帶來的帶寬消耗。
-
如何避免簽名密鑰sign_key泄露帶來的影響?
如果sign_key泄露,那么任何人都可以偽造成Server欺騙Client,因為它拿到了sign_key,它就可以簽發任何內容,Client用verify_key去驗證簽名必然驗簽成功。因此sign_key如果泄露必須要能夠對verify_key進行撤銷,重新派發新的公鑰。這其實和前一問題是緊密聯系的,前一問題是公鑰派發問題,本問題是公鑰撤銷問題。TLS是通過CRL和OCSP兩種方式來撤銷公鑰的,但是這兩種方式存在撤銷不及時或給驗證帶來額外延遲的副作用。由于mmtls是通過內置·verify_key·在客戶端,必要時通過強制升級客戶端的方式就能完成公鑰撤銷及更新。另外,sign_key是需要Server高度保密的,一般不會被泄露,對于微信后臺來說,類似于sign_key這樣,需要長期私密保存的密鑰在之前也有存在,早已形成了一套方法和流程來應對長期私密保存密鑰的問題。
-
用sign_key進行簽名的內容僅僅只包含svr_pub_key是否有隱患?
回顧一下,上面描述的帶認證的ECDH協商過程,似乎已經足夠安全,無懈可擊了,但是,面對成億的客戶端發起ECDH握手到成千上萬臺接入層機器,每臺機器對一個TCP連接隨機生成不同的ECDH公私鑰對,這里試想一種情況,假設某一臺機器某一次生成的ECDH私鑰svr_pri_key1泄露,這實際上是可能的,因為臨時生成的ECDH公私鑰對本身沒有做任何保密保存的措施,是明文、短暫地存放在內存中,一般情況沒有問題,但在分布式環境,大量機器大量隨機生成公私鑰對的情況下,難保某一次不被泄露。
這樣用sign_key(sign_key是長期保存,且分布式環境共享的)對svr_pub_key1進行簽名得到簽名值Signature1,此時攻擊者已經拿到svr_pri_key1,svr_pub_key1和Signature1,這樣他就可以實施中間人攻擊,讓客戶端每次拿到的服務器ECDH公鑰都是svr_pub_key1:客戶端隨機生成ECDH公私鑰對(cli_pub_key, cli_pri_key)并將cli_pub_key發給Server,中間人將消息攔截下來,將client_pub_key替換成自己生成的client_pub_key’,并將svr_pub_key1和Signature1回給Client,這樣Client就通過計算ECDH_Compute_Key(svr_pub_key1, cli_pri_key)=Key1, Server通過計算ECDH_Compute_Key(client_pub_key’, svr_pub_key)=Key’,中間人既可以計算出Key1和Key’,這樣它就可以用Key1和Client通信,用Key’和Server進行通信。發生上述被攻擊的原因在于一次握手中公鑰的簽名值被用于另外一次握手中,如果有一種方法能夠使得這個簽名值和一次握手一一對應,那么就能解決這個問題。
解決辦法也很簡單,就是在握手請求的ClientHello消息中帶一個Client_Random隨機值,然后在簽名的時候將Client_Random和svr_pub_key一起做簽名,這樣得到的簽名值就與Client_Random對應了。mmtls在實際處理過程中,為了避免Client的隨機數生成器有問題,造成生成不夠隨機的Client_Random,實際上Server也會生成一個隨機數Server_Random,然后在對公鑰簽名的時候將Client_Random、Server_Random、svr_pub_key一起做簽名,這樣由Client_Random、Server_Random保證得到的簽名值唯一對應一次握手。
3.2.3 mmtls對認證密鑰協商的選擇
上面一共介紹了2種1-RTT 密鑰協商方式和4種0-RTT 密鑰協商方式。
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的安全性依賴于ticket_key的安全性,因此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握手安全性依賴ticket_key,為了加強安全性,在實現上,PSK必須要限制過期時間,避免長期用同一個PSK來進行握手協商;ticket_key必須定期輪換,且具有高度機密的運維級別。
另外,為了提高系統可用性,實際上mmtls在一次成功的ECDH握手中會下發兩個PSK,一個生命周期短保證安全性,一個生命周期長保證可用性。在一次ECDH握手中,請求會帶上生命周期長的PSK(如果存在的話),后臺可根據負載情況進行權衡,選擇使用ECDH握手或者PSK握手。
3.3 Record協議 --- 使用對稱加密密鑰進行安全的通信
經過上面的Handshake過程,此時Client和Server已經協商出了一致的對稱加密密鑰pre_master_key,那么接下來就可以直接用這個pre_master_key作為密鑰,選擇一種對稱加密算法(如常用的AES-CBC)加密業務數據,將密文發送給Server。是否真的就這么簡單呢?實際上如果真的按這個過程進行加密通信是有很多安全漏洞的。
3.3.1 認證加密(Authenticated Encryption)
“加密并不是認證”在密碼學中是一個簡單的共識,但對于我們很多程序員來說,并不知道這句話的意義。加密是隱藏信息,使得在沒有正確密鑰的情況下,信息變得難以讀懂,加密算法提供保密性,上面所述的AES-CBC這種算法只是提供保密性,即防止信息被竊聽。
在信息安全領域,消息認證(message authentication)或數據源認證(data origin authentication)表示數據在傳輸過程中沒有被修改(完整性),并且接收消息的實體能夠驗證消息的源(端點認證)。AES-CBC這種加密算法只提供保密性,但是并不提供完整性。這似乎有點違反直覺,好像對端發給我一段密文,如果我能夠解密成功,通過過程就是安全的,實則不然,就拿AES-CBC加密一段數據,如果中間人篡改部分密文,只要不篡改padding部分,大部分時候仍舊能夠正常解密,只是得到的明文和原始明文不一樣。現實中也有對消息追加CRC校驗來解決密文被篡改問題的,實際上經過精心構造,即使有CRC校驗仍然能夠被繞過。本質的原因是在于進行加密安全通信過程,只使用了提供保密性的對稱加密組件,沒有使用提供消息完整性的密碼學組件。因此只要在用對稱加密算法加密明文后,再用消息認證碼算法對密文計算一次消息認證碼,將密文和消息認證碼發送給Server,Server進行驗證,這樣就能保證安全性了。
實際上加密過程和計算消息認證碼的過程,到底應該如何組合,誰先誰后,在密碼學發展的歷史上先后出現了三種組合方式:(1)Encrypt-and-MAC (2)MAC-then-Encrypt (3)Encrypt-then-MAC,根據最新密碼學動態,目前學術界已經一致同意Encrypt-then-MAC是最安全的,也就是先加密后算消息認證碼的方式。
鑒于這個陷阱如此險惡,因此就有人提出將Encrypt和MAC直接集成在一個算法內部,讓有經驗的密碼專家在算法內部解決安全問題,不讓算法使用者選擇,這就是這就是AEAD(Authenticated-Encryption With Addtional data)類的算法。TLS1.3徹底禁止AEAD以外的其他算法。mmtls經過綜合考慮,選擇了使用AES-GCM這種AEAD類算法,作為協議的認證加密組件,而且AES-GCM也是TLS1.3要求必須實現的算法。
3.3.2 密鑰擴展
TLS1.3明確要求通信雙方使用的對稱加密Key不能完全一樣,否則在一些對稱加密算法下會被完全攻破,即使是使用AES-GCM算法,如果通信雙方使用完全相同的加密密鑰進行通信,在使用的時候也要小心翼翼的保證一些額外條件,否則會泄露部分明文信息。另外,AES算法的初始化向量(IV)如何構造也是很有講究的,一旦用錯就會有安全漏洞。也就是說,對于handshake協議協商得到的pre_master_secret不能直接作為雙方進行對稱加密密鑰,需要經過某種擴展變換,得到六個對稱加密參數:
Client Write MAC Key ? ?(用于Client算消息認證碼,以及Server驗證消息認證碼) Server Write MAC Key ? (用于Server算消息認證碼,以及Client驗證消息認證碼) Client Write Encryption Key(用做Client做加密,以及Server解密) Server Write Encryption Key(用做Server做加密,以及Client解密) Client Write IV ?(Client加密時使用的初始化向量)Server Write IV ?(Server加密時使用的初始化向量)
當然,使用AES-GCM作為對稱加密組件,MAC Key和Encryption Key只需要一個就可以了。
握手生成的pre_master_secret只有48個字節,上述幾個加密參數的長度加起來肯定就超過48字節了,所以需要一個函數來把48字節延長到需要的長度,在密碼學中專門有一類算法承擔密鑰擴展的功能,稱為密鑰衍生函數(Key Derivation Function)。TLS1.3使用的HKDF做密鑰擴展,mmtls也是選用的HKDF做密鑰擴展。
在前文中,我用pre_master_secret代表握手協商得到的對稱密鑰,在TLS1.2之前確實叫這個名字,但是在TLS1.3中由于需要支持0-RTT握手,協商出來的對稱密鑰可能會有兩個,分別稱為Static Secret(SS)和Ephemeral Secret(ES)。從TLS1.3文檔中截取一張圖進行說明一下:
上圖中Key Exchange就是代表握手的方式,在1-RTT ECDHE握手方式下
ES=SS = ECDH_Compute_Key(svr_pub_key, cli_pri_key);在0-RTT ECDH下,
SS=ECDH_Compute_Key(static_svr_pub_key, cli_pri_key), ES=ECDH_Compute_Key(svr_pub_Key, cli_pri_Key);在0-RTT/1-RTT PSK握手下,
ES=SS=pre-shared key;在0-RTT PSK-ECDHE握手下,
SS=pre-shared key, ES=ECDH_Compute_Key(svr_pub_key, cli_pri_key);前面說過mmtls使用的密鑰擴展組件為HKDF,該組件定義了兩個函數來保證擴展出來的密鑰具有偽隨機性、唯一性、不能逆推原密鑰、可擴展任意長度密鑰。兩個函數分別是:
HKDF-Extract( salt, initial-keying-material )該函數的作用是對initial-keying-material進行處理,保證它的熵均勻分別,足夠的偽隨機。
HKDF-Expand( pseudorandom key, info, out_key_length )參數pseudorandom key是已經足夠偽隨機的密鑰擴展材料,HKDF-Extract的返回值可以作為pseudorandom key,info用來區分擴展出來的Key是做什么用,out_key_length表示希望擴展輸出的key有多長。mmtls最終使用的密鑰是有HKDF-Expand擴展出來的。mmtls把info參數分為:length,label,handshake_hash。其中length等于out_key_length,label是標記密鑰用途的固定字符串,handshake_hash表示握手消息的hash值,這樣擴展出來的密鑰保證連接內唯一。
TLS1.3草案中定義的密鑰擴展方式比較繁瑣,如上圖所示。為了得到最終認證加密的對稱密鑰,需要做3次HDKF-Extract和4次HKDF-Expand操作,實際測試發現,這種密鑰擴展方式對性能影響是很大的,尤其在PSK握手情況(PSK握手沒有非對稱運算)這種密鑰擴展方式成為性能瓶頸。TLS1.3之所以把密鑰擴展搞這么復雜,本質上還是因為TLS1.3是一個通用的協議框架,具體的協商算法是可以選擇的,在有些協商算法下,協商出來的pre_master_key(SS和ES)就不滿足某些特性(如隨機性不夠),因此為了保證無論選擇什么協商算法,用它來進行通信都是安全的,TLS1.3就在密鑰擴展上做了額外的工作。而mmtls沒有TLS1.3這種包袱,可以針對微信自己的網絡通信特點進行優化(前面在握手方式選擇上就有體現)。mmtls在不降低安全性的前提下,對TLS1.3的密鑰擴展做了精簡,使得性能上較TLS1.3的密鑰擴展方式有明顯提升。
在mmtls中,pre_master_key(SS和ES)經過密鑰擴展,得到了一個長度為2*enc_key_length+2*iv_length的一段buffer,用key_block表示,其中:
client_write_key = key_block[0...enc_key_length-1] client_write_key = key_block[enc_key_length...2*enc_key_length-1] client_write_IV ?= key_block[2*enc_key_length...2*enc_key_length+iv_length-1] server_write_IV ?= key_block[2*enc_key_length+iv_length...2*enc_key_length+2*iv_length-1]3.3.3 防重放
重放攻擊(Replay Attacks)是指攻擊者發送一個接收方已經正常接收過的包,由于重防的數據包是過去的一個有效數據,如果沒有防重放的處理,接收方是沒辦法辨別出來的。防重放在有些業務是必須要處理的,比如:如果收發消息業務沒有做防重放處理,就會出現消息重復發送的問題;如果轉賬業務沒有做防重放處理,就會重現重復轉賬問題。微信在一些關鍵業務層面上,已經做了防重放的工作,但如果mmtls能夠在下層協議上就做好防重放,那么就能有效減輕業務層的壓力,同時為目前沒有做防重放的業務提供一個安全保障。
防重放的解決思路是為連接上的每一個業務包都編一個遞增的sequence number,這樣只要服務器檢查到新收到的數據包的sequence number小于等于之前收到的數據包的sequence number,就可以斷定新收到的數據包為重放包。當然sequence number是必須要經過認證的,也就是說sequence number要不能被篡改,否則攻擊者把sequence number改大,就繞過這個防重放檢測邏輯了。可以將sequence number作為明文的一部分,使用AES-GCM進行認證加密,明文變長了,不可避免的會增加一點傳輸數據的長度。實際上,mmtls的做法是將sequence number作為構造AES-GCM算法參數nonce的一部分,利用AES-GCM的算法特性,只要AES-GCM認證解密成功就可以確保sequence number符合預期。
上述防重放思路在1-RTT的握手方式下是沒有問題的,因為在1-RTT握手下,第一個RTT是沒有業務數據的,可以在這個RTT下由Client和Server共同決定開始計算sequence number的起點。但是在0-RTT的握手方式,第一個業務數據包和握手數據包一起發送給服務器,對于這第一個數據包的防重放,Server只能完全靠Client發來的數據來判斷是否重放,如果客戶端發送的數據完全由自己生成,沒有包含服務器參與的標識,那么這份數據是無法判斷是否為重放數據包的。在TLS1.3給了一個思路來解決上述這個“0-RTT跨連接重放的問題”:在Server處保存一個跨連接的全局狀態,每新建一個連接都更新這個全局狀態,那么0-RTT握手帶來的第一個業務數據也可以由這個跨連接的全局狀態來判斷是否重放。但是,在一個分布式系統中每新建一個連接都讀寫這個全局狀態,如此頻繁的讀寫,無疑在可用性和性能消耗上都不可接受。
事實上,0-RTT跨連接防重放確實困難,目前沒有比較通用、高效的方案。其實在Google的QUIC crypto protocol中也存在0-RTT跨連接重放的問題,由于QUIC主要應用在Chrome瀏覽器上,在瀏覽器上訪問網站時,建連接的第一個請求一般是GET而不是POST,所以0-RTT加密的數據不涉及多少敏感性,被重放也只是刷新一次頁面而已,所以其選擇了不解決0-RTT防重放的問題。但是微信短連接是POST請求,帶給Server的都是上層的業務數據,因此0-RTT防重放是必須要解決的問題。mmtls根據微信特有的后臺架構,提出了基于客戶端和服務器端時間序列的防重放策略,mmtls能夠保證超過一段時間T的重放包被服務器直接解決,而在短時間T內的重放包需要業務框架層來協調支持防重放,這樣通過proxy層和logic框架層一起來解決0-RTT PSK請求包防重放問題,限于篇幅,詳細方案此處不展開介紹。
四、小結
mmtls是參考TLS1.3草案標準設計與實現的,使用ECDH來做密鑰協商,ECDSA進行簽名驗證,AES-GCM作為對稱加密算法來對業務數據包進行認證加密,使用HKDF進行密鑰擴展,摘要算法為SHA256。另外,結合具體的使用場景,mmtls在TLS1.3的基礎上主要做了以下幾方面的工作:
輕量級。砍掉了客戶端認證相關的內容;直接內置簽名公鑰,避免證書交換環節,減少驗證時網絡交換次數。
安全性。選用的基礎密碼組件均是TLS1.3推薦、安全性最高的密碼組件;0-RTT防重放由proxy層和logic框架層協同控制。
高性能。使用0-RTT握手方式沒有增加原有Client和Server的交互次數;和TLS1.3比,優化了握手方式和密鑰擴展方式。
高可用性。服務器的過載保護,確保服務器能夠在容災模式下提供安全級別稍低的有損服務。
五、參考資料
1. TLS協議分析與現代加密通信協議設計
(http://blog.helong.info/blog/2015/09/06/tls-protocol-analysis-and-crypto-protocol-design/)
2. RFC5246
(https://tools.ietf.org/html/rfc5246)
3. The Transport Layer Security (TLS) Protocol Version 1.3
(https://tlswg.github.io/tls13-spec/)
轉載于:https://www.cnblogs.com/zafu/p/9245914.html
總結
以上是生活随笔為你收集整理的转:基于TLS1.3的微信安全通信协议mmtls介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JQData数据提取及MySQL简单操作
- 下一篇: 面试技能之软知识(HR面试问题回答)