碼了 2000 多行代碼就是為了講清楚 TLS 握手流程(續)

2021-02-20 Go 夜讀

在「碼了2000多行代碼就是為了講清楚TLS握手流程」這一篇文章的最後挖了一個坑,今天這篇文章就是為了填坑而來,因此本篇主要分析TLS1.2的握手流程。

在寫前一篇文章時,筆者的Demo只支持解析TLS1.3握手流程中發送的消息,寫本篇時,筆者的Demo已經可以解析TLS1.x握手流程中的消息,有興趣的讀者請至文末獲取Demo源碼。

結論先行

為保證各位讀者對TLS1.2的握手流程有一個大概的框架,本篇依舊結論先行。

單向認證

單向認證客戶端不需要證書,客戶端驗證服務端證書合法即可訪問。

下面是筆者運行Demo列印的調試信息:

根據調試信息知,TLS1.2單向認證中總共收發數據四次,Client和Server從這四次數據中分別讀取不同的信息以達到握手的目的。

筆者將調試信息轉換為下述時序圖,以方便各位讀者理解。

雙向認證

雙向認證不僅服務端要有證書,客戶端也需要證書,只有客戶端和服務端證書均合法才可繼續訪問(筆者的Demo如何開啟雙向認證請參考前一篇文章中HTTPS雙向認證部分)。

下面是筆者運行Demo列印的調試信息:

同單向認證一樣,筆者將調試信息轉換為下述時序圖。

雙向認證和單向認證相比,Server發消息給Client時會額外發送一個certificateRequestMsg消息,Client收到此消息後會將證書信息(certificateMsg)和籤名信息(certificateVerifyMsg)發送給Server。

雙向認證中,Client和Server發送的消息變多了,但是總的數據收發仍然只有四次。

總結

1、單向認證和雙向認證中,總的數據收發僅四次(比TLS1.3多一次數據收發),單次發送的數據中包含一個或者多個消息。

2、TLS1.2中除了finishedMsg其餘消息均未加密。

3、在TLS1.2中,ChangeCipherSpec消息之後的所有數據均會做加密處理,它的作用在TLS1.2中更像是一個開啟加密的開關(TLS1.3中忽略此消息,並不做任何處理)。

和TLS1.3的比較消息格式的變化

對比本篇的時序圖和前篇的時序圖很容易發現部分消息格式發生了變化。下面是certificateMsg和certificateMsgTLS13的定義:

// TLS1.2
type certificateMsg struct {
raw []byte
certificates [][]byte
}
// TLS1.3
type certificateMsgTLS13 struct {
raw []byte
certificate tls.Certificate
ocspStapling bool
scts bool
}

其他消息的定義筆者就不一一列舉了,這裡僅列出格式發生變化的消息。

TLS1.2TLS1.3certificateRequestMsgcertificateRequestMsgTLS13certificateMsgcertificateMsgTLS13消息類型的變化

TLS1.2和TLS1.3有相同的消息類型也有各自獨立的消息類型。下面是筆者例子中TLS1.2和TLS1.3各自獨有的消息類型:

TLS1.2TLS1.3serverKeyExchangeMsg-clientKeyExchangeMsg-serverHelloDoneMsg--encryptedExtensionsMsg消息加密的變化

前篇中提到,TLS1.3中除了clientHelloMsg和serverHelloMsg其他消息均做了加密處理,且握手期間和應用數據使用不同的密鑰加密。

TLS1.2中僅有finishedMsg做了加密處理,且應用數據也使用該密鑰加密。

TLS1.3會計算兩次密鑰,Client和Server讀取對方的HelloMsg和finishedMsg之後即可計算密鑰。

「Client和Server會各自計算兩次密鑰,計算時機分別是讀取到對方的HelloMsg和finishedMsg之後」,這是前篇中的描述,計算時機描述不準確以上面為準。

TLS1.2隻計算一次密鑰,Client和Server分別收到serverKeyExchangeMsg和clientKeyExchangeMsg之後即可計算密鑰,和TLS1.3不同的是TLS1.2密鑰計算後並不會立即對接下來發送的數據進行加密,只有當發送/接受ChangeCipherSpec消息後才會對接下來的數據進行加解密。

生成密鑰過程

TLS1.2和TLS1.3生成密鑰的過程還是比較相似的, 下圖為Client讀取serverKeyExchangeMsg之後的部分處理邏輯:

圖中X25519是橢圓曲線迪菲-赫爾曼(Elliptic-curve Diffie–Hellman ,縮寫為ECDH)密鑰交換方案之一,這在前篇已經提到過故本篇不再贅述。

根據Debug結果,本例中ka.preMasterSecret和TLS1.3中的共享密鑰生成邏輯完全一致。不僅如此,在後續的代碼分析中,筆者發現TLS1.2也使用了AEAD加密算法對數據進行加解密(AEAD在前篇中已經提到過故本篇不再贅述)。

下圖為筆者Debug結果:

圖中prefixNonceAEAD即為TLS1.2中AEAD加密算法的一種實現。

這裡需要注意的是TLS1.3也會計算masterSecret。為了方便理解,我們先回顧一下TLS1.3中生成masterSecret的部分源碼:

// 基於共享密鑰派生hs.handshakeSecret
hs.handshakeSecret = hs.suite.extract(hs.sharedKey,
hs.suite.deriveSecret(earlySecret, "derived", nil))
// 基於hs.handshakeSecret 派生hs.masterSecret
hs.masterSecret = hs.suite.extract(nil,
hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil))

由上易知,TLS1.3先通過共享密鑰派生出handshakeSecret,最後通過handshakeSecret派生出masterSecret。與此相比,TLS1.2生成masterSecret僅需一步:

hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)

masterFromPreMasterSecret函數的作用是利用HMAC(HMAC在前篇中已經提到故本篇不再贅述)算法對Client和Server的隨機數以及共享密鑰進行摘要,從而計算得到masterSecret。

masterSecret在後續的過程中並不會用於數據加密,下面筆者帶各位讀者分別看一下TLS1.3和TLS1.2生成數據加密密鑰的過程。

TLS1.3生成數據加密密鑰(以Client計算serverSecret為例):

serverSecret := hs.suite.deriveSecret(hs.masterSecret,
serverApplicationTrafficLabel, hs.transcript)
c.in.setTrafficSecret(hs.suite, serverSecret)

前篇中提到hs.suite.deriveSecret內部會通過hs.transcript計算出消息摘要從而重新得到一個serverSecret。setTrafficSecret方法內部會對serverSecret計算得到AEAD加密算法所需要的key和iv(初始向量:Initialization vector)。

因此可知TLS1.3計算密鑰和Client/Server生成的隨機數無直接關係,而與Client/Server當前收發的所有消息的摘要有關。

補充:IV通常是隨機或者偽隨機的。它和數據加密的密鑰一起使用可以增加使用字典攻擊的攻擊者破解密碼的難度。例如,如果加密數據中存在重複的序列,則攻擊者可以假定消息中相應的序列也是相同的,而IV就是為了防止密文中出現相應的重複序列。

參考:

https://whatis.techtarget.com/definition/initialization-vector-IV 

https://en.wikipedia.org/wiki/Initialization_vector

TLS1.2生成數據加密密鑰:

clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromMasterSecret(tr.vers, suite, p.masterSecret, tr.clientHello.random, tr.serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)
serverCipher = hs.suite.aead(serverKey, serverIV)
c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)

前文中提到masterSecret的生成與Client和Server的隨機數有關,而通過keysFromMasterSecret計算AEAD所需的key和iv依舊與隨機數有關。

小結:

1、本例中TLS1.2和TLS1.3均使用X25519算法計算共享密鑰。

2、本例中TLS1.2和TLS1.3均使用AEAD進行數據加解密。

3、TLS1.3通過共享密鑰派生兩次才得到masterSecret,而TLS1.2以共享密鑰、Client和Server的隨機數一起計算得到masterSecret。

4、TLS1.3通過消息的摘要再次計算得到一個數據加密密鑰,而TLS1.2直接通過masterSecret計算得到AEAD所需的key和iv。

TLS1.1和TLS1.0不支持HTTP2

在前面提到本文的例子已經支持解析TLS1.x的握手流程,這個時候筆者突然很好奇瀏覽器還支持那些版本的TLS協議。

然後筆者在谷歌瀏覽器上首先測試了TLS1.1的服務,為了方便測試筆者改造了之前伺服器推送的案例:

server := &http.Server{Addr: ":8080", Handler: nil}
server.TLSConfig = new(tls.Config)
server.TLSConfig.PreferServerCipherSuites = true
server.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "h2", "http/1.1")
// 服務端支持的最大tls版本調整為1.1
server.TLSConfig.MaxVersion = tls.VersionTLS11
server.ListenAndServeTLS("ca.crt", "ca.key")

運行Demo後得到如下截圖:

圖中紅框部分obsolete的意思筆者也不知,正好學習一波(技術人的英語大概就是這樣慢慢積累起來的吧)。

這下筆者明白了,TLS1.1已經不被支持所以頁面才無法正常訪問,然而事實真是如此嘛?

直到幾天後筆者開始寫這篇文章時,內心仍是十分疑惑,於是使用了curl命令再次訪問。

圖中藍框部分正是TLS1.1的握手流程,有興趣的讀者可以使用筆者的例子和curl -v命令進行雙向驗證。

圖中紅框部分提示說「HTTP2的數據發送失敗」,筆者才恍然大悟,將上述代碼作如下微調後頁面可正常訪問。

server.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "http/1.1")

經過筆者的測試,TLS1.0同TLS1.1一樣均不支持HTTP2協議,當然這兩個協議也不推薦繼續使用。

寫在最後

「紙上得來終覺淺,絕知此事需躬行」。筆者不敢保證把TLS握手流程的每個細節都講的十分清楚,所以建議各位讀者去github克隆代碼,然後自己一步一步Debug必然能夠加深印象並徹底理解。當然,順便關注或者star一下這種隨手為之的小事,筆者相信各位讀者還是十分樂意的~

最後,衷心希望本文能夠對各位讀者有一定的幫助。

註:

寫本文時, 筆者所用go版本為: go1.15.2

文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/http2/tls/main.go

長按二維碼關注公眾號,查看更多分享。


不會吧,不會吧,不會真的有人白嫖吧。素質三連(分享、點讚、在看)都是筆者持續創作更多優質內容的動力!

相關焦點

  • Golang TLS雙向身份認證DoS漏洞分析(CVE-2018-16875)
    /post/id/168383一、前言如果程序原始碼使用Go語言編寫,並且用到了單向或者雙向TLS認證,那麼就容易受到CPU拒絕服務(DoS)攻擊。四、漏洞分析背景知識為了便於漏洞分析,我們舉個簡單的例子:TLS客戶端連接至TLS伺服器,伺服器驗證客戶端證書。
  • TLS握手:回顧1.2、迎接1.3
    另外,在我查找握手細節的過程中發現很多博客沒有把一些細節解釋清楚,主要是關於RSA和DH兩種密鑰協商協議所產生的一些細節上的區別,所以就有了這篇文章。至此握手完成。Master Secret無論用哪種密鑰協商算法,最後的主密鑰計算方法是相同的——TLS採用了一個PRF來做主密鑰的計算,PRF的參數就是之前的兩個隨機數加上預主密鑰。
  • GTIN碼查詢、獲取流程及申請費用詳解
    GTIN名詞解釋: GTIN是全球貿易項目代碼(Global Trade Item Number),用作識別商品品項的全球性獨一編碼,是編碼系統中應用最廣泛的標識代碼。 這種編碼叫EAN/UCC-14代碼結構,對應ITF-14條碼。它的編碼規則是單品的EAN/UCC-13代碼加包裝指示符。
  • 使用ANSI轉義碼實現一個終端命令行界面
    這其實都是使用ANSI標準轉義碼編程實現的,今天蟲蟲就給大家來說明ANSI轉義碼的使用方法,最後還使用Python語言實現一個簡單命令行界面。顏色在*nix體系下數程序與終端交互的方式是通過ANSI轉義碼。這些轉義碼是作為程序可列印的特殊代碼,以便擴展終端的顯示。
  • Python代碼可以加密碼?Python字節碼告訴你!
    通常Python是以原始碼形式發布的,不過對於一些敏感信息,不希望以原始碼形式發布,就可以用字節碼形式發布。當然,字節碼也可以被反編譯。為了讓Python原始碼更安全,可以製作自己的私有Python環境,這些內容我們後面再說。相信很多沒接觸過過Python字節碼的同學一定有很多疑問,那麼就繼續看後面的內容吧!
  • 多比特信號跨時鐘域握手方案
    讀完這篇文章後我也整理了一下其中涉及的代碼,可以作為一個通用ip使用,後臺回復「握手」即可下載,所有權歸原作者所有。本篇文章我們來看一下多比特數據跨時鐘握手控制,本文將從以下幾點來總結:什麼是握手信號握手指的是兩個設備之間通信的一種方式,用來通信的信號就是握手信號。
  • (附破解文件和代碼)
    第七種:還有一種就是把 Mac 與 Ipad 結合,基於 Mac 與 Ipad 協議,非 grpc,mmtls 破解,功能合適,微信第三方最穩定通道,不會出現技術封號問題;1.2 本文主要會講兩種基於 PC Hook
  • 離職程式設計師交接工作被同事懟:每行代碼都必須講清楚,要不然投訴
    給負責交接的同事講清楚工作不是必須要做的工作,但也是職業素養的體現。一般來說,員工離職之前都會跟同事講清楚工作內容,可是有一位程式設計師在離職的時候被交接同事要求一行一行講代碼,不講就投訴他,這到底是怎麼一回事呢?
  • 重學TCP/IP協議和三次握手四次揮手
    為了使不同體系結構的計算機網絡都能互聯,國際標準化組織 ISO 於1977年提出了一個試圖使各種計算機在世界範圍內互聯成網的標準框架,即著名的開放系統互聯基本參考模型 OSI/RM,簡稱為OSI。OSI 的七層協議體系結構的概念清楚,理論也較完整,但它既複雜又不實用,TCP/IP 體系結構則不同,但它現在卻得到了非常廣泛的應用。
  • 牛皮了,頭一次見有大佬把TCP三次握手四次揮手解釋的這麼明白
    TCP/IP 是一個四層體系結構,它包含應用層,傳輸層,網際層和網絡接口層(用網際層這個名字是強調這一層是為了解決不同網絡的互連問題),不過從實質上講,TCP/IP 只有最上面的三層,因為最下面的網絡接口層並沒有什麼具體內容,因此在學習計算機網絡的原理時往往採用折中的辦法,即綜合 OSI 和 TCP/IP 的優點,採用一種只有五層協議的體系結構,這樣既簡潔又能將概念闡述清楚,有時為了方便,也可把最底下兩層稱為網絡接口層
  • 《孤島驚魂5》錯誤代碼granite2000怎麼辦?錯誤代碼granite解決辦法
    導 讀 孤島驚魂5錯誤代碼granite2000怎麼辦?很家都對這個情況表示頭疼,遊戲也無法正常運行。
  • 程式設計師將一萬五千行代碼精簡到四百行,卻讓10多個人丟了工作
    代碼寫得多反而代表編程能力差?網上一個網友曬出了自己同事寫的代碼,語氣透露出一絲不滿:稍微放大看看:可以看到,這是一段蠻長的Java代碼。一類覺得這是「教科書式」的代碼行,整潔規範:編寫者的思維邏輯也非常清晰,代碼工整度沒毛病,運行起來應該也沒什麼大礙,所以有網友覺得好像沒什麼大礙。但還有網友感覺寫這麼多是為什麼?是因為按代碼行數算薪資?有畫蛇添足的味道,感覺有點湊數的嫌疑。
  • 字節碼層面理解try、catch、finally
    面試中經常有關於try、catch、finally相關的問題,今天從字節碼層面了解他們的運行流程。在這裡我們通過jclasslib查看編譯後的字節碼,並找到方法的字節碼指令,如下圖:右邊被圈中的就是test()方法執行的字節碼指令,字節碼指令較長,接下來一部分一部分的分析。
  • 讓SSL/TLS協議流行起來:深度解讀SSL/TLS實現
    本系列的文章大體分為3個部分:SSL/TLS協議的基本流程典型的針對SSL/TLS協議的攻擊SSL/TLS協議的安全加固措施本文對SSL/TLS協議概況做基本介紹,包括SSL/TLS協議的版本變遷,協議的詳細流程以及流行的SSL/TLS協議實現。
  • 徐文兵講黃帝內經:經典重啟 新篇再續
    走進小院子裡,你會看到有人在海棠樹下站樁、打拳;再走進教室,你會發現有人在伏案寫毛筆字;如果適逢徐文兵先生的課,那他一定在講《黃帝內經》。先生的課,一如既往的精彩。教室裡時不時響起的郎朗笑聲、陣陣的掌聲就是很好的說明。原來,徐先生默默地在這裡教書育人,風雨不輟。十年來,他一直深耕在中醫教育的第一線。即便是沒有教學課的工作日,徐先生會在厚樸中醫診所出診,身旁總是少不了跟診實踐的學生。
  • 百行代碼縮成十行,蘋果開發者大會上亮相的Swift UI成了入門碼農禮物
    不過,最令碼農開心的是蘋果發布了Swift UI程式語言,可以將一百行代碼簡化為大約十幾行,大大降低了碼農入門的門檻。iPad有了自己的作業系統iPad誕生多年,扮演的一直是大屏版iPhone的角色,即便它的一些界面跟iPhone有所區別。
  • 英語流行詞:就是要你好看的「加強級握手」
    新東方網>英語>英語學習>語法詞彙>流行語>正文英語流行詞:就是要你好看的「加強級握手」   還記得加拿大總理特魯多和美國總統川普的經典握手場面嗎?其實握手也是很有學問的,握手太無力會顯得性格軟弱,握手太用力又會顯得太強勢。
  • 離職程式設計師交接工作被同事懟:每一行代碼都講清楚,不然投訴
    這位網友稱自己是一名程式設計師,前段時間在提出了離職之後,與同事進行工作交接,自己已經將之前所有的設計文檔全都交給了同事,還表示他可以先看代碼,如果看不懂的話隨時可以來找自己。
  • 一「碼」當先!我國統一社會信用代碼基本全覆蓋
    查詢企業的「信用身份證」——統一社會信用代碼就可知道。目前,我國統一社會信用代碼已基本實現全覆蓋,18位代碼正逐步發揮強大的信用力量。  多碼改一碼,減輕企業負擔也承載企業信用  1位登記管理部門代碼、1位機構類別代碼、6位登記管理機關行政區劃碼、9位主體標識碼、1位校驗碼,與公民身份證號一樣,統一社會信用代碼也有18位。