TLS是一個信道建立和信道的表達方式,向下依託於TCP,向上對應用程序服務。隨著TLS的發展,DTLS也開始出現,就是同樣的TLS邏輯被應用於UDP業務之上。由於客戶端的網路千奇百怪,TCP需要普遍長連接的時候幾乎就是不靠譜的代名詞,肯定是要斷線重連機制的。如果處理的不好,還很容易形成SYN攻擊或者過多的耗費伺服器資源。因此固定長連接的客戶端業務,例如遊戲場景,大都傾向於使用UDP作為連接承載,在這種情況下,DTLS就有了很大的用武之地。UDP是一種基礎協議,很多人選擇了UDP通常也並沒有明確原因,很多是基於不想去處理TCP那麼多顯而易見的麻煩的事情。但是最後用了UDP之後,發現要做的事情甚至比TCP麻煩。但是UDP相對於TCP的一個巨大的優勢是UDP相當於四層的IP協議,在此之上可以構造更複雜的取代TCP的協議,而不用改動內核代碼。市場上確實是UDP也在大量的使用。所以DTLS也仍然有市場。

TLS信道建立的過程是一個雙方溝通能力的過程。所有的連接都一定是客戶端首先發起的,所以TLS握手的第一個消息肯定是客戶端發送的。如果我們來設計協議,我們首先要從TLS的目的來設計整個通信的過程,以最少的通信成本來達到TLS的目的。在整個TLS握手的過程中,客戶端需要發送自己支持的應用層列表,支持的密碼學套件等交底信息,並且還要產生來源於客戶端的隨機數,完成屬於客戶端部分的密碼學建立過程。對於服務端來說,事情也是一樣的,但是有一個巨大的區別就是服務端需要發送證書給客戶端。這個需求模型是最常見的CS需求模型。所以如果根據這個需求模型進行TLS握手的設計,僅僅需要一個RTT,客戶端發送一次,服務端發送一次,之後就可以通信了。事實上,TLS1.3就是用這個思路設計的。只是在TLS1.2的時候,設計者活脫脫的設計了兩次RTT。其中有個重要的考慮是重協商的支持,但是這個重協商很快就被證明有安全問題,所以即使在TLS1.2的時代,也是一直處於關閉狀態,在TLS1.3中就直接取消了。這個協議的設計中,還可以傳輸客戶端證書,可以傳輸OCSP擴展消息等一些對標準握手流程的修改,但是總體的TLS1.2的握手還是很清爽的。

訪問百度的ASCII包內容

訪問百度的數據包流程

我們觀察訪問百度的握手流程。百度的TLS集羣系統從技術上講是非常的高效的。目前也是TLS1.2的流程。從ASCII中,我們能看到在握手的開始出現了兩個能辨識的字元串,一個是域名,一個是應用層協議列表。這部分是客戶端發送給服務端的,這裡的域名就是SNI機制。SNI機制允許同一個埠提供多個可以選擇的證書,在客戶端的ClientHello中會攜帶SNI擴展,裡面就有選擇的域名信息。然後服務端通過域名信息就可以選擇出正確的服務證書。第二個可以識別的字元串就是支持的上層應用的列表,也就是ALPN機制,用以告訴服務端跟本客戶端的通信可以選擇這些不同的應用層協議。之後的伺服器回復裡麪包含了大量的字元串,這些字元串就是證書裡面的內容了。實際上ALPN的意義只對協商有意義,通常客戶端可能版本比較老,但是伺服器一般可以支持比較新的技術。如果伺服器支持到最新的技術,完全可以直接通過收到的數據的格式直接判斷是什麼協議的數據,從而不需要ALPN。

我們能看到的最明顯的幾個內容是CA的名字,百度的企業名字和這個證書裏包含的域名列表。業務層面溝通的核心信息就在這個明文的ASCII了。但是除此之外,還有很多密碼學相關的不可讀的內容,這些繁雜的密碼學演算法我們先不去關心細節。只需要知道客戶端和服務端雙方在使用特定的密碼學演算法之前都必須各自提供隨機性,也就是分別生成隨機數。這個隨機數從理論上並不一定需要,但是從安全上就一定需要。隨機,是安全世界的基石。

從雙方的數據包上可以看到這是一次典型的正常的TLS握手過程。客戶端一個ClientHello,交代一下自己的信息,然後服務端回一個ServerHello,根據自己的能力核對並且回應選擇客戶端發來的備選方案。緊跟著的是證書,這裡傳送的證書就是在瀏覽器裡面看到的證書文件,在瀏覽器可以把這個證書用證書導出程序導出為一個文件,到處之後的文件就會發現與伺服器上的證書文件是一模一樣的。可見幾乎就是一個明文的傳輸(只是存儲上使用了簡單的Base64編碼)。這一步的證書傳輸是我最不能忍受的,我問了無數遍為什麼不壓縮?後來發現了TLS本來是有壓縮功能的,但是被全世界因為安全問題給禁止了。這樣就很尷尬,這麼大的一個明文證書,在每一次TLS握手請求的時候都要全量傳輸。伺服器的流量消耗是非常驚人的。雖然有各種復用方法,但是這也不能彌補初次建立TLS連接的流量損耗問題的本質。而且很關鍵的問題是這個壓縮需要客戶端和服務端同時支持,哪怕雙方都同時發送一個gzip壓縮過格式的證書,對於流量的節省來說也是成倍的提高(但是會有一次解壓縮的代價)。理論上如此容易的事情被無限制的推遲,這也就是標準的尷尬。如果專用的客戶端就完全可以自己來做這種證書的優化。如果把TLS不看成是一個標準,而是一個技術參考,那麼就可以任意的修改。很多大型企業的內部通信協議都是通過這種思路修改得來的,萬變不離其宗。

證書之後就是一個Server Key Exchange的消息,這個消息是用於真正的密碼學運算的消息。緊接著就是Server Change Cipher消息,這個消息根據定義是不屬於TLS握手。它對應的數據傳輸層協議的格式標誌也不是TLS握手的標誌,對應的,TLS握手的完整性檢查也就不會作用在這個消息上。因為緊跟著這個消息的就是一個抓包不能顯示的消息。這個消息叫做FInish消息,是服務端和客戶端對握手至今的所有數據的二進位層面的哈希計算的結果。這一步的主要功能是一個放置MIM(Man in the Middle)的中間人攻擊。任何人在握手過程的中間修改了握手的內容,都是可以被這個哈希校驗檢查出來的,只要不是客戶端和服務端同時在協議的層面進行修改,就可以從協議的設計層面完全的組織中間人的改包攻擊。但是這也不是說就是不能中間人。很多企業都是在企業出口的時候裝載了TLS Termination裝置。你認為你在和百度進行HTTPS通信,其實自己看證書,信任的卻是公司的內部證書。這是因為公司在出口的時候完整的把內外的TLS請求進行了代理解包,然後使用自己作為中介跟內外通信。這整個過程是完全公開的,因為TLS協議本身是不可能允許他做到不留痕跡。典型的表現就是我們需要信任企業的證書,因為跟我們溝通的不是真實的對端證書代表的身份,而是企業的共同的出口身份。這樣企業就可以完整的解包HTTPS流量並且進行審計了。

整個協議在設計的時候有一個比較有意思的必要消息,就是這個Change Cipher Spec消息,服務端和客戶端都需要發。我們在設計集羣的時候,我們知道是不能在中間修改TLS握手的數據包的,原因是Finish校驗,但是這個Change Cipher Spec卻並不參加Finish校驗。所以我們在集羣中可以相對寬鬆的利用這個消息,修改這個消息,甚至是代理這個消息。這個消息裡面並沒有什麼密碼學相關的內容,只是一個純粹的通知。這個通知的意思是,我這邊已經完成了密碼學上下文的溝通,分對稱機密已經結束了,我已經使用剛才溝通的參數成功的建立了對稱加密的信道。所以在OpenSSL裏,發布這個消息的同時是對應著對稱加密密碼學上下文的建立的。我們看到客戶端在收到服務端的Key Exchange消息之後緊跟一個Change Cipher Spec,客戶端也會回復一個Client Key Exchange消息,緊跟著就是客戶端的Change Cipher Spec和Finish消息,這個意義與服務端是相同的。Key Exchange消息就是非對稱加密協商對稱加密信道的核心邏輯,用於交換密碼學參數的關鍵步驟。

在TLS1.2的握手過程中,密碼學參數的溝通是一個主要的功能。我們知道TLS1.2的時候非對稱加密演算法已經只剩下橢圓曲線和RSA了,這兩種都可以用於密鑰交換和證書籤發。但是出於安全性考慮,RSA已經逐漸不再被應用到密鑰交換的應用中了,但是RSA證書是仍然有應用的。RSA如果作為密鑰交換,如果有人在中間進行抓包,當伺服器的私鑰泄漏之後,中間所有的流量都可以被解密。這是因為RSA通過作為密鑰交換時,服務端生成的關鍵密碼學參數是由伺服器的私鑰加密過然後傳輸給客戶端的。如果私鑰泄漏,並且中間的抓包結果被保存,就可以直接解出歷史數據。但是如果使用橢圓曲線,由於橢圓曲線的中間密碼學參數是從來不會在信道中傳輸的,相關上下文都是在客戶端和服務端本地保存,所以即使被完全抓包,並且私鑰泄漏,之前的TLS連接的數據也不會丟失。這就是橢圓曲線在安全性上顯著優於RSA的地方。所以用在密鑰交換的時候,RSA已經基本讓位於ECDHE,ECDHE已經成為目前幾乎唯一可以選擇的密鑰交換方式。在證書的方面,橢圓曲線簽發的證書的大小是遠小於RSA證書的,橢圓曲線的sign速度(私鑰加密數據的速度)是遠大於RSA的sign速度的,差別在十倍的數量級。但是RSA的verify速度是遠大於橢圓曲線的veirfy速度的,差別也在十倍的數量級。由於一般在伺服器端存儲證書,證書在TLS握手的整個過程中都是提供一個sign的操作功能,而verify的操作是在用戶端,所以大部分伺服器會傾向於使用橢圓曲線證書,同時減小大小和提高sign的速度。

我們就以ECDSA為案例,分析一遍TLS握手的密碼學相關流程。典型案例仍然是訪問百度的抓包。

這是ClientHello中攜帶的支持的密碼學套件的列表,可以看到客戶端優先支持的都是橢圓曲線,其次纔是RSA的證書,再其次纔是RSA的密鑰交換。一個很關鍵的密碼學參數就是客戶端的隨機數。這個隨機數的隨機性是密碼學保密性的關鍵,有28個位元組。隨後服務端在回復ServerHello的時候,選擇了一個密碼學套件,同時也回復了一個28個位元組的隨機數,這個隨機數的隨機性要求必須要與客戶端發來的隨機數沒有任何關係。

接下來,伺服器發送了證書,也就是certificate消息給客戶端。這個證書本質上是一個公鑰,一個證書必須同時有公鑰和私鑰,公鑰是發送出去任意使用的,私鑰是解密別人使用自己的公鑰加密的數據的內容的。這裡傳輸的證書一個密碼學上的最重要的意義是將公鑰傳輸給客戶端,如此後續客戶端的消息就可以使用公鑰加密傳輸回服務端。

緊跟著ServerHello把自己的隨機數也發送給客戶端之後,Server就需要構造並且傳輸非對稱加密演算法需要的參數了。這裡就是Server Key Exchange,裡面的最重要的參數就是ECDHE演算法需要構造的密碼學參數。可以看到一個是橢圓曲線的名字:secp256r1。這個橢圓曲線不但在HTTPS中,還在比特幣中有廣泛的應用,所有的橢圓曲線實際上都是方程:y2=x3+ax+b 。這裡面兩個參數,這個橢圓曲線是一大類的曲線,對於secp256r1來說,a=0, b=7。所以對於secp256r1來說,這條曲線是y2=x3+7。a,b參數的取值並不是隨便取的,必須要滿足一定的條件,並且一條密碼學曲線都是固定取值的。不同的a,b會得到非常不一樣形狀的圖形,不同的圖形有不同的用途,我們這裡討論的secp256r1曲線的圖形如下:

這條曲線的形狀如圖。在密碼學上,所有的橢圓曲線都是使用的有限域版本GF(p),所以p也是一個橢圓曲線的一個值。對於secp256k1來說,他的p=2^256-2^32-2^9-2^8-2^7-2^6-2^4-1,橢圓曲線的方程是y2=x3+7 mod p。

橢圓曲線之所以被選擇出來是因為他有眾多非常有意思的特性,例如在橢圓曲線上拉一條直線,經過三個點,那三個點的和是0(那就看曲線怎麼定義和這個操作了,這裡是數論裏的阿貝爾羣)。本質上,使用橢圓曲線是使用了橢圓曲線的性質來得到一個數學運算系統,最終在參與密碼學計算的是數論系統,與橢圓曲線就沒有太大關係了,但是仍然可以用曲線來理解,因為數論運算是基於橢圓曲線構造的。

當這個橢圓曲線演算法用於非對稱加密的密鑰交換的時候,我們知道兩個人都能看到對方的公鑰,並且都知道自己的私鑰。橢圓曲線演算法在使用的時候能夠看到與RSA的一個最大的不同,就是RSA需要一個私鑰,橢圓曲線並不需要。對於橢圓曲線做密鑰交換,每一次通信的私鑰和公鑰都是臨時生成的,這也是橢圓曲線比RSA安全性高的原因。因為RSA一旦私鑰泄漏,歷史的加密數據都能破解,而橢圓曲線不能。每一次通信都用完全不同的私鑰公鑰對進行信道協商。所以對於橢圓曲線來說,在每一次通信的時候都會首先生成這個私鑰和公鑰,生成私鑰的方法就是在橢圓曲線上取一個點,根據上面說過的三點和等於0的特性,讓兩個點重合,重合之後,這條曲線就是橢圓的切線,與橢圓相交於兩個點。非切點的那個點就是私鑰,切點取反再多切線得到一個新的切點,如果多次取反做切點就得到了公鑰點。Q=NG,G是私鑰點,N是做切線的次數,Q是公鑰。已知Q,算不出G,就是橢圓曲線生成公鑰私鑰的原理了。這裡面對於橢圓曲線來說,N是一個公開的常數,雙方都知道並且相同,也就是說,這個數學難題是已知了一個點,求N次反向的求切線運算得到的那個點。這個點是計算難度上得不出來的。也就是說已知一點曲線上的切點,得到切線的難度比已知一個隨意點,對曲線做切線的計算度複雜很多,多到多次計算就是計算不可能問題。

當橢圓曲線生成的公鑰和私鑰用於交換的時候,因為雙方已經互換了公鑰,這個計算過程就變成了雙方同時用自己的私鑰乘以對方的公鑰。橢圓曲線的神奇特性保證了得到的結果是一樣的,也就是說是得到了同一個點。這個點就是協商得到的對稱加密的密鑰。

這個其實也是很好理解。Q1*G2=Q1*(Q2*N)=Q1*N*Q2=G1*Q2=對稱加密的密鑰

所以我們看到在Server Key Exchange中的pubkey域就是這裡生成的公鑰,客戶端隨後也會回復一個動態生成的公鑰,雙方都發送了公鑰就相當於互相傳遞了對稱加密的密鑰,並且外人是無法破解的。我們可以看到對於橢圓曲線的握手(ECDHE),在Server Key Exchange一步還有一個哈希結果的傳遞,這一步在RSA握手中是沒有的(整個Server Key Exchange步驟在RSA協商中都是沒有的)。因為雙方有個本質的區別是ECDHE的握手過程是沒有私鑰參與的,使用的所謂的私鑰是動態生成的臨時隨機數。而RSA的握手方式是一定要配置私鑰的,這個私鑰就是證書的私鑰。這也從另一方面說明瞭RSA的握手方式只能用RSA的證書。RSA握手的信道建立的過程中包含了證書驗證的過程,而ECDHE的驗證過程需要在這一步單獨提供。從抓包的結果能看到伺服器使用的rsa_pkcs1_sha256的簽名方法給出了一個簽名,這就是普通的證書籤名,就是用證書私鑰加密的結果從公鑰可以解密。客戶端收到這個簽名之後用公鑰解密就能得到是否是預先共享的內容,例如客戶端提前發送的隨機數,從而就能做到服務端驗證工作。

隨後客戶端也會回復一樣的Client Key Exchange,還有雙方互換的非握手流程的Change CIpher數據包。這個Change Cipher是不攜帶任何數據的,也不參與最後的哈希完整性計算。只是一個宣告信道開始的流程。最後雙方都會發送一個已經用協商好的對稱加密密鑰加密過的握手數據包,就是TLS Finish。這個TLS Finish數據包保證了整個握手過程中,數據不能被篡改。任何中間人改動了中間的某一個數據,都逃脫不掉最後的哈希運算的檢查。因為這個哈希運算是完整的計算從握手開始到結束的所有內容的。


推薦閱讀:
相關文章