SSL協議是現代網路通訊中重要的一環,它提供了傳輸層上的資料安全。為了方便大家的理解,本文將先從加密學的基礎知識入手,然後展開對SSL協議原理、流程以及一些重要的特性的詳解,最後會擴充套件介紹一下國密SSL協議的差異、安全性以及TLS 1.3的關鍵新特性。

限於篇幅以及個人知識水平,本文不會涉及過於細節的內容。特別地,本文將不涉及演算法的具體原理,也不涉及實際程式碼的實現。而是試圖以圖表等直觀的方式來了解基本的原理以及流程。

本部落格已經遷移至CatBro's Blog,那裡是我自己搭建的個人部落格,頁面效果比這邊更好,支援站內搜尋,評論回覆還支援郵件提醒,歡迎關注。這邊只會在有時間的時候不定期搬運一下。

本篇文章連結

密碼學基礎

古典的密碼

密碼學的歷史可以追溯到很久以前,早在羅馬共和國時期,據說凱撒就使用凱撒密碼和他的將軍進行通訊。

凱撒密碼

凱撒密碼就是一個簡單地移位操作。凱撒金鑰非常容易被破解,使用暴力破解的方式把金鑰從0到25都嘗試一遍就可以了。

簡單替換密碼

簡單替換密碼,其明文字母表和密文字母表之間是一種隨機的對映關係。這種方式金鑰空間為26! ~= 4 * 10^26,這已經無法使用暴力破解方式來找到正確的金鑰。但是可以使用頻率分析來破譯。

Enigma密碼機

二戰時期德國使用的一系列轉子機械加解密機器。儘管此機器的安全性較高,但盟軍的密碼學家們還是成功地破譯了大量由這種機器加密的資訊。

主要弱點:

  • 將通訊密碼連續輸入兩次並加密
  • 通訊密碼是人為選定
  • 必須派發國防軍密碼本

對稱密碼

塊密碼和流密碼

上面幾種密碼其實都屬於對稱密碼的範疇,對稱加密演算法可以分為塊密碼和流密碼兩種:

  • 塊密碼(block cipher):每次只能處理特定長度的資料塊。一個塊的長度就叫塊長度(分組長度)
  • 流密碼(stream cipher):對資料流進行連續處理的一類密碼演算法。一般以1位元、8位元或32位元為單位進行加密和解密。

AES (Advanced Encryption Standard)

AES是目前最常用的對稱演算法之一。對稱加密演算法,顧名思義,就是加密和解密使用相同的金鑰。傳送方使用金鑰K對明文P進行加密得到密文C,然後將密文C傳送給接收方,接收方使用相同的金鑰K對密文進行解密,得到明文P。

AES採用的是Rijndael演算法,下圖是Rijndael加密中一輪的操作:

每一輪都會進行位元組替換、行位移、列混合和輪金鑰異或,使得輸入的位得到充分的混淆。一個塊的加密會經過很多輪的操作,最終得到密文。

因為這4輪操作都是可逆的,解密的時候就是一個相反的過程。

塊密碼的模式

模式

分組密碼演算法只能加密固定長度的分組,但是我們需要加密的明文長度可能會超過分組密碼的分組長度,這時就需要對分組密碼演算法進行迭代,以便將一段很長的明文全部加密。而塊與塊之間進行迭代的方法就稱為分組密碼的模式(mode)

常用模式由:ECB、CBC、OFB、CFB、CTR等。這邊限於篇幅,僅介紹ECB、CBC、CTR三種。

ECB模式

ECB是最簡單的一種模式,每個塊是獨立進行加密的。將明文分組加密之後的結果直接成為密文分組。當最後一個明文分組的內容小於分組長度時,需要用一些特定的資料進行填充(padding)。

ECB模式有非常顯著的缺點:同樣的明文塊會被加密成相同的密文塊;因此,它不能很好的隱藏資料模式。在某些場合,這種方法不能提供嚴格的資料保密性。

比如下面的企鵝圖,用ECB模式加密之後得到中間的圖,還是能很明顯地看出圖片的輪廓,不能很好的保護資料的機密性。而用其他模式加密之後,就得到如第三個圖所示的結果,已經看不出明顯的特徵。

CBC模式

CBC模式中,每個明文塊先與前一個密文塊進行異或,再進行加密。所以每個明文塊都依賴於前面的所有明文塊。同時,為了保證每條訊息的唯一性,在第一個塊中需要使用初始化向量(IV)。

CBC是TLS1.2時代最為常用的工作模式。它沒有ECB模式那個相同原文加密稱相同密文的問題,但是也因此導致其平行計算能力不如ECB模式。

CTR模式

計數器模式其實是將塊加密轉換成了流加密,它對一個逐次累加的計數器進行加密,然後用加密的位元序列與明文進行XOR操作得到密文。

計數器的作用跟CBC模式中的IV類似,保證相同的明文會加密成不同的密文。相比CBC等模式,CTR模式還有如下優點:它非常適合平行計算、錯誤密文中的對應位元只會影響明文中的對應位元。

AEAD(Authenticated Encryption with Associated Data)

前面的那些模式都已經被發現存在不同程式的缺點或問題。在TLS1.3時代,只保留了AEAD型別的加密模式。AEAD在加密的同時增加了認證的功能,常用的有GCM、CCM、Ploy1305。

GCM(Galois/Counter Mode)

GCM中的G是指的GMAC(關於MAC我們稍後會講到),C就是指的我們前面提到的CTR計數器模式。下圖右上角的部分就是上面的Counter模式加密,剩餘部分則是GMAC。最終的結果包含初始計數器值、加密密文和MAC值。

公鑰密碼(非對稱密碼)

在使用對稱加密時,一定會碰到金鑰分發(金鑰交換)的問題。使用預先共享金鑰具有侷限性,需要一種安全的方式將金鑰交給對方。於是就引出了公鑰密碼。

公鑰密碼演算法(RSA、國密SM2)

最常用的公鑰金鑰演算法要數大名鼎鼎的RSA了,國密中則有SM2演算法。公鑰密碼有兩個金鑰,其中一個是公開金鑰,公開金鑰可以散佈,另一個是私有金鑰,需要自己嚴密保管。比如Bob要發訊息給Alice,Bob用Alice的公鑰對訊息進行加密,然後傳送給Alice,Alice則用自己的私鑰進行解密。

Diffie-Hellman金鑰交換

另一種常用的公鑰密碼是DH類演算法,可以用下圖來形象地解釋DH演算法的原理。

首先雙方協商一個相同的底色(演算法引數),然後各自生成自己私有的顏色(相當於私鑰),並通過混合得到對應的公有顏色(相當於公鑰)。隨後雙方交換各自的公有顏色,並與自己的私鑰顏色混合,最終協商出一個相同的顏色(即交換的金鑰)。竊聽者就算得到了雙方交換的這些資訊,也無法生成相同的金鑰,求解離散物件問題的困難度保證了DH演算法的安全性。

ECDH和ECDHE

ECDH是基於橢圓曲線的DH演算法,原理上跟DH基本一樣,主要是把有限域上的模冪運算替換成了橢圓曲線上的點乘運算。相比DH演算法速度更快,可逆更難。

DH和ECDH都是使用的一個固定金鑰,一旦金鑰洩漏,以前所有的密文訊息就都都破解了。ECDHE則提供了前向安全性,它每次使用一個臨時的金鑰,基於這個臨時金鑰進行金鑰交換生成會話金鑰。就算這個臨時金鑰洩漏了,也隻影響本次SSL會話的訊息。

單向雜湊函式(Hash)

前面的對稱金鑰和公鑰密碼解決了資訊傳輸的機密性問題,使我們傳輸的資訊不被竊聽。但是還沒有解決完整性的問題,訊息有可能在中途被“篡改”。所以就輪到單向雜湊函數出場了。

單向雜湊函式可以根據輸入的資訊,計算出一個固定長度的雜湊值(摘要值),這個雜湊值可以作為訊息的指紋用於檢查訊息的完整性。修改了原始訊息的任意1個位元,最終生成的雜湊值可能就完全不同了。

理想的雜湊函式具有以下幾個性質:

  1. 確定性:同樣的訊息總是產生同樣的雜湊值
  2. 任何給定訊息能夠快速計算出雜湊值
  3. 單向性:無法通過雜湊值反算出訊息
  4. 弱抗碰撞性:難以找到可以生成給定雜湊值的訊息
  5. 強抗碰撞性:難以找到可以生成兩條相同雜湊值的訊息
  6. 訊息的一個小的改動會導致Hash值的巨大變化

訊息認證碼(MAC)

單向雜湊函式雖然保證了訊息的完整性,但是聰明的攻擊者可以將訊息連同其雜湊值一起篡改了,而接收方卻無法進行識別。所以還需要對訊息進行認證,傳統的認證方法有手寫簽名、蓋章、手印、身份證、口令(其實是個共享金鑰)等。在密碼領域則可以通過訊息認證碼的方法進行認證。訊息認證碼相比單向雜湊函式,多了一個共享金鑰對訊息進行認證。攻擊者因為沒有這個金鑰,所以無法偽造出MAC值。

MAC的問題

  • MAC跟對稱密碼一樣需要一個共享金鑰,所以也會有金鑰分發的問題。
  • 無法對第三方證明
  • 無法防止否認

數字簽名(RSA、ECDSA)

為了解決MAC的問題,又引入了數字簽名。

數字簽名同樣屬於公鑰密碼的範疇。跟之前不同的地方是,它使用私鑰進行加密(該操作叫作簽名),任何人都可以用公鑰進行解密(該操作叫作驗籤)。因為公鑰是公開的,所以解決了第三方證明的問題。又因為私鑰只有本人具有,沒有私鑰的人事實上無法生成這段密文,所以也可以防止否認。所以MAC的三個問題都可以由數字簽名解決。

通常配合雜湊函式使用,同時保證了完整性,也加快了速度。如下圖所示,Alice傳送訊息給Bob。她先把自己的公鑰傳送給Bob,接著她對訊息先計算一個雜湊值,然後用自己的私鑰對這個雜湊值進行加密得到訊息的簽名值。然後她將初始的訊息和簽名一同傳送給Bob。Bob收到之後,對訊息進行同樣的雜湊函式計算得到雜湊值,同時用Alice的公鑰對簽名資料進行解密,得到解密後雜湊值,然後在比較兩個計算得到雜湊值是否一致,以驗證簽名的有效性。

數字證書、 CA

到目前為止,我們的公鑰密碼、數字簽名解決了機密性、完整性、可以進行認證以及防止否認等。但其實這一切都是基於一個大前提:就是公鑰是屬於真正的傳送者。如果公鑰是偽造的、那麼這一切就都失效了。前面提到的數字簽名只能保證對方擁有這個公鑰對應的私鑰,但是沒法認證公鑰所有者本身的身份。

為了解決這個問題,數字證書應運而出。它的解決方式就是讓一個可信的第三方來對公鑰進行簽名。這個可信的第三方,我們一般稱之為Certificate authorities(CA)

我們來看看實際證書是什麼樣子的。其中Subject是證書擁有者的資訊,Issuer就是其簽發者的資訊,Subject Public Key Info是實際的公鑰資訊,這裡用的是RSA 2048位的金鑰。證書最後就是CA用自己的私鑰對這個證書的簽名。任何擁有CA的證書(包含公鑰)的人都可以對這個證書進行驗證,從而驗證公鑰所有者的身份。Validity是這個證書的有效期。

$ openssl x509 -in ~/sharefile/certs/rsa-user.pem -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
a2:95:90:e8:e0:f6:3a:e4
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=SH, L=Shanghai, O=Awesome Software, OU=SSL Group, CN=RSA_CA_Cert/[email protected]
Validity
Not Before: Nov 5 01:47:05 2018 GMT
Not After : Nov 2 01:47:05 2028 GMT
Subject: C=CN, ST=SH, L=Shanghai, O=Awesome Software, OU=SSL Group, CN=RSA_USER/[email protected]
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:f4:a0:b6:57:e7:68:39:98:39:13:11:b9:61:ee:
1d:5c:60:c6:53:51:ec:0e:59:53:0e:12:75:1c:5b:
b1:b7:e4:dd:4d:c8:c1:b8:eb:07:32:12:a1:fc:50:
33:d9:63:4a:30:7f:b4:b5:e9:87:d9:71:33:65:ab:
56:54:ba:34:92:06:9a:af:ff:84:8e:a3:29:af:46:
61:3b:45:39:c5:a8:0a:f9:ae:fb:d4:2b:20:5a:9d:
ce:00:fe:c9:87:b2:f4:d2:f8:bf:b7:2e:7a:79:5e:
94:54:7d:f9:09:73:4a:ec:c7:22:01:79:4c:62:11:
55:a9:d3:3b:f8:ef:3e:1d:56:e3:2f:34:ef:c6:0f:
2c:94:40:01:a3:6f:1e:3b:61:bd:79:00:bf:26:f7:
d6:c3:a6:22:22:50:f0:6e:aa:03:1e:ea:d9:e6:ad:
4d:a9:60:4f:6d:81:80:f4:f9:a9:89:31:ab:f1:a3:
9b:1a:a2:57:ed:44:30:39:fc:3a:3a:c3:6e:c8:a6:
db:2e:14:c1:3b:6b:5b:ca:ab:b7:0d:fb:85:39:08:
bf:6b:41:c1:f6:42:b1:3f:9c:45:5c:4c:37:8e:7d:
c6:18:f8:9b:87:16:80:7f:25:34:8f:14:a9:02:2e:
7c:07:c9:8a:21:77:33:03:6a:9c:86:f1:73:9c:c8:
2d:f1
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
Signature Algorithm: sha256WithRSAEncryption
30:cf:b2:9f:50:ea:d7:0d:e2:87:50:e6:bd:d7:b0:17:12:31:
b1:9f:59:16:50:60:bc:52:c0:46:7a:43:d0:34:43:48:d0:bd:
e1:a0:dc:75:a2:60:a0:c9:8f:ed:d4:36:14:18:75:c0:ef:c3:
92:fa:43:fa:34:5a:12:77:2f:03:00:eb:a6:db:d9:6b:50:ff:
44:56:22:c6:51:73:73:9a:4b:fd:bb:53:ff:2b:7e:97:55:d3:
4d:bb:bd:26:69:37:8d:71:30:41:bf:fd:48:40:fc:6f:cd:e2:
b7:4a:90:6f:a2:11:85:a7:88:d3:61:d5:03:0a:50:98:cc:0e:
aa:d5:83:38:b4:d5:f0:06:ff:a5:eb:d4:e6:54:14:e9:65:af:
36:a5:e4:3e:8b:78:18:0b:d6:7c:cc:f1:a3:da:7a:03:fd:89:
23:f0:e1:3e:af:7b:b1:7a:53:82:11:4a:5e:1d:84:b6:0b:cc:
96:b4:3a:8a:43:cf:ff:b3:3a:be:47:e0:40:c0:48:15:b4:f3:
2d:2b:73:b8:07:d2:21:83:3c:c4:4c:c2:31:17:4e:4c:15:da:
66:fd:06:9a:b7:ed:b5:9e:71:a3:40:0b:39:12:3c:7b:cb:cb:
a0:af:d0:c7:fe:59:41:35:04:7f:f3:f3:38:d0:d0:ac:7a:15:
7e:fa:ee:fd

在實際使用中,通常會使用多級證書鏈,每一級證書由上一級CA簽發,並由上一級CA的證書進行驗籤。最終的Root CA證書只能由它自己簽發的,也即用自己的私鑰對自己的公鑰進行簽名,否則就無限遞迴了。

所以數字證書實際上是一種信任鏈的傳遞,將對眾多個體的身份認證轉移到對少數幾個CA的身份認證,可以減少遭到中間人攻擊的風險。從公鑰密碼和證書這些出發就引出了公鑰基礎設定(PKI):這是為了能夠更有效地運用公鑰而制定的一系列規範和規格的總稱。

混合密碼系統

那麼有了公鑰密碼,是不是就不需要對稱密碼了呢?公鑰密碼雖然解決了對稱密碼的金鑰分發問題,但是在計算速度上卻遠低於對稱密碼,相差幾個數量級。下面是用openssl speed測試RSA1024和AES128的結果。AES128(相同塊大小)的速度大概是RSA1024簽名的1200倍,是驗籤的70倍。

$ openssl speed rsa1024
sign verify sign/s verify/s
rsa 1024 bits 0.000103s 0.000006s 9732.8 162578.8 $ openssl speed -evp aes128
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes 16384 bytes
aes-128-cbc 1075884.36k 1452740.12k 1510392.06k 1530545.83k 1537414.49k 1537114.11k

因為對稱密碼和非對稱密碼各有優缺點,所以在實際應用中通常將兩者結合起來,使用非對稱密碼來完成金鑰交換,然後生成會話金鑰作為對稱密碼的金鑰,使用對稱密碼對資訊進行加解密。

隨機數發生器

截止目前,我們已經解決了很多問題,包括機密性、完整性、認證性、防抵賴,但其實還有一個大問題我們沒有解決:即我們的會話金鑰如何生成?一個安全的演算法其所有的安全性應該都是基於其金鑰的安全性,如果我們的金鑰可以被輕易破解或預測,那我們前面構建的一切就都土崩瓦解了。所以隨機數發生器就在這裡起到了至關重要的作用。

隨機數同樣在防重放攻擊中具有重要的作用,所謂重放攻擊,就是攻擊者將竊聽的資料儲存下來,在後面再原樣傳送給接收者,以達到其特定的攻擊目的。防重放的常用手段有使用序號、時間戳、隨機數等。

這裡留個問題給大家思考,因為機密性和完整性的保護,攻擊者既不能解密訊息,又不能對訊息進行篡改,那麼重放有什麼用呢?

理想的隨機數發生器具有以下特性:

  • 隨機性:不存在統計學偏差,是完全雜亂的數列
  • 不可預測性:不能從過去的數列推測出下一個出現的數
  • 不可重現性:除非將數列本身儲存下來,否則不能重現相同的數列

金鑰派生函式 key derivation function

KDF可用於將金鑰材料擴充套件為更長的金鑰或獲取所需格式的金鑰。TLS 1.2中是用的PRF演算法,TLS 1.3中則是用的HKDF演算法。

SSL/TLS協議詳解

什麼是SSL/TLS協議

好了,有了前面的密碼學基礎,我們就可以正式進入TLS協議的介紹。前面介紹的基本都是獨立的演算法或者是幾個演算法結合起來的元件,而SSL/TLS協議則是基於這些底層的演算法原語和元件,最終拼裝而成的一個成品的密碼學協議

SSL全稱是Secure Sockets Layer,安全套接字層,它是由網景公司(Netscape)設計的主要用於Web的安全傳輸協議,目的是為網路通訊提供機密性、認證性及資料完整性保障。如今,SSL已經成為網際網路保密通訊的工業標準。

SSL最初的幾個版本(SSL 1.0、SSL2.0、SSL 3.0)由網景公司設計和維護,從3.1版本開始,SSL協議由因特網工程任務小組(IETF)正式接管,並更名為TLS(Transport Layer Security),發展至今已有TLS 1.0、TLS1.1、TLS1.2這幾個版本。目前主流的還是TLS1.2,不過TLS1.3即將是大勢所趨。

Protocol Published Status
SSL 1.0 Unpublished Unpublished
SSL 2.0 1995 Deprecated in 2011 (RFC 6176)
SSL 3.0 1996 Deprecated in 2015 (RFC 7568)
TLS 1.0 1999 Deprecated in 2020 (RFC 8996)[8][9][10]
TLS 1.1 2006 Deprecated in 2020 (RFC 8996)[8][9][10]
TLS 1.2 2008
TLS 1.3 2018

SSL/TLS協議能夠提供的安全目標主要包括如下幾個:

  • 機密性:藉助加密防止第三方竊聽
  • 認證性:藉助數字證書認證伺服器端和客戶端身份,防止身份偽造
  • 完整性:藉助訊息認證碼(MAC)保障資料完整性,防止訊息篡改
  • 防重放:防止重放攻擊

協議分層

相信大家對TCP/IP 5層模型已經非常熟悉了,TLS協議就如其名字所說的(Transport Layer Security),用於保障傳輸層的安全。它位於傳輸層之上,應用層之下。

SSL/TLS協議有一個高度模組化的架構,其內部又分為很多子協議:Handshake協議、Alert協議、ChangeCipherSpec協議、Application協議。它們底層都是基於Record協議,記錄層協議負責識別不同的上層訊息型別以及訊息的分段加密認證等。

  • Handshake協議:包括協商安全引數和演算法套件、伺服器身份認證(客戶端身份認證可選)、金鑰交換
  • Application協議:用於傳輸應用層資料
  • ChangeCipherSpec 協議:一條訊息表明握手協議已經完成
  • Alert 協議:對握手協議中一些異常的錯誤提醒,分為fatal和warning兩個級別,fatal型別的錯誤會直接中斷SSL連線,而warning級別的錯誤一般情況下SSL連線會繼續,只是會給出錯誤警告

SSL/TLS協議被設計為一個兩階段協議,分為握手階段應用階段

握手階段:也稱協商階段,在這一階段主要目標就是進行我們前面已經提到過的協商安全引數和演算法套件、身份認證(基於數字證書)以及金鑰交換生成後續加密通訊所使用的金鑰。

應用階段:雙方使用握手階段協商好的金鑰進行安全通訊。

SSL record

SSL記錄層包的格式,跟其下的IP或TCP層類似,所有在SSL會話上交換的資料都按如下格式封裝成幀。記錄層協議負責識別不同的訊息型別,也負責對訊息的分段、壓縮、訊息認證和完整性保護、加密等工作。

一個典型的記錄層工作流(分組密碼演算法)如下:

  • 記錄層接收到應用層的資料
  • 將接收到的資料分塊
  • 使用協商的MAC key計算MAC或HMAC,並增加到記錄塊中
  • 使用協商的Cipher Key將記錄資料加密

當加密的資料到了接收端,對方則進行相反的操作:解密資料、驗證MAC、重組資料並交給應用層。

所有這些工作都由SSL層完成了,對於上層應用是完全透明的。

對於握手階段的訊息,payload是明文,當然就沒有MAC和Padding。其他訊息的payload都是密文。

對於流加密演算法,沒有後面的padding。對於塊加密演算法的記錄,根據使用的演算法在payload之前有可選的IV欄位。

對於AEAD演算法,因為認證已經包含在演算法裡了,所以沒有後面的MAC和Padding欄位。payload之前有一個外部nonce欄位。

演算法套件CipherSuites

在深入握手流程之前,我們先來了解一下演算法套件的概念。前面在密碼學基礎部分我們已經瞭解到了各種演算法,包括認證演算法、金鑰交換演算法、對稱密碼演算法、完整性認證的演算法。

TLS 1.2-

一個演算法套件是一個SSL連線中用到的這些演算法型別的組合,包含如下幾個部分:

  • Key Exchange(Kx)
  • Authentication(Au)
  • Encryption(Enc)
  • Message Authentication Code(Mac)

常見的演算法套件型別比如TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256TLS_RSA_WITH_AES_256_GCM_SHA384TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384ECC_SM2_WITH_SM4_SM3(國密)、ECDHE_SM2_WITH_SM4_SM3(國密)。

ECDHE-ECDSA-AES128-GCM-SHA256為例,前面的ECDHE表示金鑰交換演算法,ECDSA表示身份認證演算法,AES128-GCM表示對稱加密演算法,其中128表示塊長度,GCM是其模式,SHA256表示雜湊演算法。對於AEAD因為訊息認證和加密已經合併到一起了,最後的SHA256只表示金鑰派生函式的演算法,而對於傳統的資料加密和認證分開的演算法套件,它還表示MAC的演算法。

可以通過如下命令檢視每種演算法套件的詳細情況:openssl ciphers -V | column -t | less

TLS 1.3

對於TLSv1.3,因為已經將金鑰交換和身份認證演算法從演算法套件中獨立出去,演算法套件只表示加密演算法和金鑰派生函式。之所以要獨立出去,是因為支援的演算法越來越多了,導致相乘之後演算法套件數量龐大。

可以用如下命令檢視當前支援哪些TLS 1.3演算法套件:openssl ciphers -V | column -t | grep 'TLSv1.3'

SSL handshake

終於進入核心的握手協議部分了。如前所述,SSL握手主要幹以下幾件事情:首先得商量雙方用什麼演算法套件,然後根據需要認證對方的身份(基於數字證書),最後基於選擇的演算法套件進行金鑰交換生成後續加密通訊所使用的金鑰。(本節描述的是TLS 1.2及以前版本的情況)

下圖是一個完整SSL握手的建立流程:

首先是TCP的3次握手建立TCP連線,然後客戶端發起SSL握手。一次完整的SSL握手包含兩次互動,第一次主要是完成演算法套件的選擇。第二次互動主要是完成身份認證以及金鑰交換。這些都協商完成之後,SSL的安全通道就建立完成了。後續的應用資料就會在安全通道上進行加密傳輸。

金鑰交換流程-RSA

基於RSA的金鑰交換流程如下:

我們來模擬下協商過程:

  • 客戶端 :Hi,服務端,我這邊支援這些演算法,這是我本次的隨機數。
  • 服務端 :好的,我看看,我們就選用這個演算法套件吧,這是我本次的隨機數,這是我的證書,你用這個證書裡的公鑰來加密預主金鑰。
  • 客戶端 :稍等我驗下證書,嗯,的確是服務端的證書。這是用證書中公鑰加密的預主金鑰。(使用兩個隨機數+預主金鑰計算主金鑰,然後生成會話金鑰。。。)好了,我這邊OK了。
  • 服務端 :收到。(用私鑰解密出預主金鑰,使用兩個隨機數+預主金鑰計算主金鑰,然後生成會話金鑰。。。)好了,我這邊也OK了。
  • 客戶端 :這是加密的應用資料。。。
  • 服務端 :這是加密的應用資料。。。

注:上面的流程是單向認證(服務端沒有驗證客戶端的身份),如果服務端也需要驗客戶端的身份,會在第一次互動中傳送Certificate Request訊息,客戶端相應的在第二次互動中傳送自己的Certificate以及CertificateVerify訊息給服務端。

金鑰交換流程-DH

基於DH的金鑰交換流程如下:

我們同樣來模擬下協商過程:

  • 客戶端 :Hi,服務端,我這邊支援這些演算法,這是我本次的隨機數。

  • 服務端 :好的,我看看,我們就選用這個演算法套件吧,這是我本次的隨機數,給你我的證書。我這邊用這個DH引數,這是對應的簽名。

  • 客戶端 :稍等我驗下證書和簽名,嗯,的確是服務端的證書,簽名也沒有問題。這是我這邊的DH引數。(使用DH引數推匯出預主金鑰,再使用兩個隨機數+預主金鑰計算主金鑰,然後生成會話金鑰。。。)好了,我這邊OK了。

  • 服務端 :收到。(使用DH引數推導預主金鑰,再使用兩個隨機數+預主金鑰計算主金鑰,然後生成會話金鑰。。。)好了,我這邊也OK了。

  • 客戶端 :這是加密的應用資料。。。

  • 服務端 :這是加密的應用資料。。。

金鑰生成

通過第一次ClientHello和ServerHello的互動、以及金鑰交換的過程,客戶端和服務端雙方,都得到了client隨機數、server隨機數以及預主金鑰。接下來就可以通過這些來計算主金鑰了。

master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];

得到主金鑰之後,再將其擴充套件為一個安全位元組序列。

 key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);

然後分別切分為MAC金鑰、對稱加密的key和IV。

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

畫成圖的話大概是下面這個樣子。

Session重用

SSL握手額外引入了兩次互動以及CPU密集型的演算法運算。每次連線都進行SSL握手時非常耗費效能的,有沒有什麼辦法進行效能優化呢?顯然提升硬體效能、軟體效能都是有效的方法。其實SSL從協議層面也考慮了這個問題,它提供了“會話重用”的特性。雙方在建立前一次SSL連線之後,可以將SSL會話儲存下來。下一次想要在建立SSL連線的時候,可以直接恢復之前的SSL會話,從而簡化SSL握手流程。Session重用的時候只需要進行一次SSL握手互動,而且不需要再進行身份認證和金鑰交換,從而大幅減小了整個流程的延遲和計算開銷。

事實上,如果瀏覽器要對同個站點發起多個連線,它通常就會等第一個SSL握手完成後再發起其他的連線,這樣其他的連線就可以複用之前的那個Session。

Session重用有兩種機制,分別為Session IDs和Session tickets。

Session ID

我們來看下使用Session ID時的Session重用流程。從前面金鑰交換的流程圖中可以看到,服務端在Server Hello訊息中會發送本次會話的Session ID給客戶端。完成握手之後,服務端會儲存本次的Session。

後續客戶端可以通過恢復這個Session來建立SSL連線。具體做法就是在ClientHello訊息中帶上要恢復的Session ID,然後服務端會根據Session ID去查詢對應的會話,如果一切都OK的話,就會根據儲存的會話恢復出會話金鑰等資訊。如果服務端不支援會話重用、或者沒找到Session ID、或者會話已經過期了,那麼就退化到完整的SSL握手。

Session Ticket

Session ID機制它要求服務端儲存每個客戶端的session cache,這會在服務端造成幾個問題:記憶體的額外開銷、要求Session的儲存和淘汰策略、然後對有多個伺服器的站點,如何高效能地共享session cache提出了挑戰。

Session Ticket機制的提出就是為了解決Session ID的問題。Session Ticket不需要服務端儲存每個客戶端的會話。取而代之,如果客戶端宣稱它支援session ticket,服務端會發送給客戶端一個New Session Ticket訊息,其中包含了會話相關的加密資料,加密的金鑰只有服務端知道。

客戶端會儲存這個session ticket,當需要會話恢復的時候,它會在ClientHello訊息的SessionTicket擴充套件中帶上這個session ticket。服務端在收到之後解密出其中的session資料從而恢復上次的會話。

所以Session Ticket機制對於服務端來說是一種無狀態(stateless)的重用,不需要服務端儲存session cache,當然也就沒有多伺服器同步的問題。

證書的吊銷(黑名單)

我們在介紹證書的時候已經看到了證書有一個有效期,在有效期外是無法驗證通過的。但是如果我們想在有效期內就讓證書失效該怎麼辦?比如公司員工離職了、或者私鑰洩漏等,總不可能把發出去的證書收回來吧。

所以就引入了證書的吊銷,CA可以吊銷某張證書,在驗證證書有效性的時候進行額外的黑名單驗證。黑名單的驗證有如下幾種機制:

Certificate Revocation List (CRL)

CRL,即證書吊銷列表。CA機構維護一個被吊銷證書的列表,裡面是被吊銷證書的資訊。驗證證書方需要下載這個列表,進行黑名單的驗證。

CRL的缺陷也很明顯:證書驗證方必須下載這個列表,且下載的列表跟實際CA機構的列表之間可能不同步。如果一個證書實際已經被吊銷,但是並沒有在本地列表中,就可能形成安全隱患。

Online Certificate Status Protocol (OCSP)

OCSP,即線上證書狀態協議。它通過線上請求的方式來進行黑名單驗證,不需要下載整個 list,只需要將該證書的序列號傳送給 CA 進行驗證。部署 OCSP 對 CA也引入了一定的要求,CA 需要搭建的一個高效能的伺服器來提供驗證服務。萬一伺服器掛掉了,那所有的黑名單驗證就會當成是通過,有一定的安全隱患。

OCSP Stapling

OCSP Stapling是OCSP標準的一個擴充套件,主要目標就是提升效能和安全性。證書擁有者自己定期向OCSP伺服器傳送請求。獲取OCSP響應,這個響應是基於時間戳的,而且由CA直接簽名。

OCSP Stapling提升整體的效能,一方面證書驗證方不需要再直接請求CA的伺服器去查詢狀態,另一方面CA的OCSP伺服器的壓力也減小了。

Server Name Indication(SNI)

當一個站點上部署了多個Server時(相當於一個IP映射了多個域名),不同的Server可能需要使用不同的證書。問題是如何在SSL握手階段知道是訪問那個host(還沒到HTTP階段,無法用請求頭裡的HOST欄位),從而決定使用對應的證書呢?

SNI就是為了解決這個問題,具體做法是在ClientHello擴充套件中帶上SNI,服務端就能從中得知需要訪問哪個host,從而選擇相應的證書。

GMSSL協議差異

GMSSL修改自TLS1.1,總體上與TLS協議的差異不大。詳見《GMT 0024-2014 SSL VPN技術規範》。

協議號

TLSv1.0, TSLv1.1, TLSv1.2、TLSv1.3的協議號分別為0x03010x03020x03030x0304

而國密的版本號是0x0101

演算法套件

新增了國密的SM1/2/3/4等演算法,定義了多個演算法套件,其中比較常用的如ECC_SM4_SM3ECDHE_SM4_SM3ECDHE_SM4_SM3要求必須雙向認證。

其中ECC_SM4_SM3的金鑰交換過程類似RSA的金鑰交換過程,由客戶端用服務端的公鑰對預主金鑰進行加密後傳送給服務端。ECDHE_SM4_SM3的金鑰交換過程與普通TLS的ECDHE金鑰交換類似,預主金鑰由客戶端和服務端雙方推導得出。ECC_SM4_SM3ECDHE_SM4_SM3的身份認證是都是通過SM2的簽名/驗籤來完成。

雙證書體系

證書訊息

國密SSL採用雙證書體系:一個簽名證書、一個加密證書。其中籤名證書用於身份認證、加密證書用於金鑰交換。傳送Certificate訊息時需要同時傳送兩個證書,格式與標準TLS報文格式一樣,第一個證書是簽名證書、第二個證書是加密證書。

ECC_SM4_SM3金鑰交換

因為採用了雙證書體系,在SSL狀態機上略微有點不同。ECC_SM4_SM3的金鑰交換過程如下:服務端傳送Certificate訊息之後,還要傳送一個ServerKeyExchange訊息(這跟RSA金鑰交換有所不同),ServerKeyExchange中包含了一個簽名值,簽名由服務端簽名證書對應的私鑰(簽名私鑰)進行計算,簽名的內容包括了ClientHello和ServerHello中的隨機數以及加密證書。

客戶端驗證證書和簽名之後,使用服務端加密證書加密預主金鑰,傳送給服務端,服務端則由自己的加密私鑰進行解密得到預主金鑰。

ECDHE_SM4_SM3金鑰交換

ECDHE_SM4_SM3的金鑰交換過程如下:服務端傳送Certificate訊息之後,同樣傳送一個ServerKeyExchange訊息,ServerKeyExchange中包含了一個簽名值,簽名由服務端簽名證書對應的私鑰(簽名私鑰)進行計算。簽名的內容跟ECC_SM4_SM3時有所不同,包括了ClientHello和ServerHello中的隨機數以及服務端ECDH引數(曲線、公鑰)。國密ECDHE的金鑰推導計算方式跟TLS也有所不同,TLS只需要對方的臨時公鑰和自己的臨時私鑰參與計算,而國密需要對方的臨時公鑰和固定公鑰(即加密證書中的公鑰)及自己的臨時私鑰和固定私鑰(即加密私鑰)參與計算。所以國密ECDHE必須是雙向認證,因為服務端在進行金鑰推導的時候也需要用到客戶端的加密證書。

客戶端驗證證書和簽名之後,傳送自己的證書給服務端,接著根據ServerKeyExchange中ECDH引數資訊生成自己的臨時金鑰,然後同自己的加密金鑰、服務端臨時公鑰和加密公鑰一起進行金鑰推導得到預主金鑰。並將自己的臨時金鑰引數通過ClientKeyExchange訊息傳送給服務端,服務端同樣使用自己的臨時金鑰、加密金鑰、客戶端的臨時公鑰和加密公鑰一起進行金鑰推導得到預主金鑰。

因為是雙向認證,客戶端在傳送ClientKeyExchange訊息之後,還需要傳送一個CertificateVerify訊息,簽名的內容為從ClientHello訊息開始到目前為止的所有已經交換的握手訊息。

安全性

常見攻擊

有些是針對協議設計上的漏洞,有些則是針對實現的bug,經常結合降級攻擊手段一起使用。因為篇幅原因,這裡僅挑選兩個有代表性的重協商攻擊和Heartbleed著重介紹一下,對於其餘攻擊感興趣的可以參考rfc7457-已知攻擊總結

  • 重協商攻擊

中間人在不需要劫持、解密SSL/TLS連線的情況下,成功地將自己偽造的資料插入到使用者真正資料之前。中間人如果瞭解APP協議(如HTTPS)的話,則會精心構造不完整的資料,讓伺服器的APP程式認為發生粘包,將資料暫緩不處理,繼續等待後續的資料上來。例如攻擊者先發送了如下的半拉子請求

GET /bank/sendmoney.asp?acct=attacker&amount=1000000 HTTP/1.1
X-Ignore-This:

後面當客戶端傳送過來真正的請求

GET /ebanking HTTP/1.1
Cookie: validcookie

APP程式將請求拼接,真正的請求頭被遮蔽了,但是卻保留了使用者的Cookie資訊,從而利用使用者的Cookie去訪問網站內容。服務端會以為前面傳送的請求是真正的客戶端傳送的。

GET /bank/sendmoney.asp?acct=attacker&amount=1000000 HTTP/1.1
X-Ignore-This: GET /ebanking HTTP/1.1
Cookie: validcookie

這個漏洞成因在於,客戶端認為的首次協商卻被伺服器認為是重協商,以及首次協商和重協商之間缺少關聯性。解決辦法就是禁用重協商或者使用安全重協商。安全重協商會增加一個安全重協商標誌,以及確認首次協商和重協商的關聯性校驗,從而確保中間人的攻擊行為可以被識別並拒絕,保證重協商安全。

我們來看看安全重協商是如何保證安全的,對於前面的ClientHello2裡面不攜帶安全重協商表示的情況:

對於前面的ClientHello2裡面攜帶安全重協商表示的情況:

無論哪種情況都能保證中間人無機可乘。而這個安全重協商的標識就是提供了一個新的擴充套件性renegotiation_info。因為SSLv3/TLS 1.0不支援擴充套件,所以提供了另一種方法,即在演算法套件列表中加上TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0xFF),這個不是一個真正的演算法套件,只是起標識作用。

安全重協商的流程如下:

  1. 在連線建立第一次SSL握手期間,雙方通過renegotiation_info擴充套件或SCSV套件通知對方自己支援安全重協商
  2. 然後在握手結束之後,client和server都分別記錄Finish訊息之中的client_verify_dataserver_verify_data
  3. 重協商時client在ClientHello中包含client_verify_data,server在ServerHello中包含client_verify_dataserver_verify_data。對於受害者,如果協商中不會攜帶這些資料則連線無法建立。而Finished訊息由於是加密的,攻擊者無法得到client_verify_data和server_verify_data的值。

這是OpenSSL庫一個實現上的bug,而不是TLS協議本身的bug。這個bug是由於TLS heartbeat擴充套件的實現沒有進行正確的輸入驗證(缺少邊界檢查)導致的。bug的命名也從heartbeat而來。由於沒有進行邊界檢查,導致可以讀取的資料超出允許的範圍。

這個bug當前造成了非常廣泛的影響,有調查顯示在這個漏洞公佈後幾年,仍有許多網站暴露在此攻擊之下。這個bug告誡我們就算協議是安全的,實現仍然可能引入安全問題。安全性就像一個木桶,整體的安全性取決於最短的那塊木板。

  • CRIME和BREACH攻擊

這兩個攻擊都是基於壓縮演算法,通過改變請求正文,對比被壓縮後的密文長度,可以破解出某些資訊。

CRIME通過在受害者的瀏覽器中執行JavaScript程式碼並同時監聽HTTPS傳輸資料,能夠解密會話Cookie,主要針對TLS壓縮。

Javascript程式碼嘗試一位一位的暴力破解Cookie的值。中間人元件能夠觀察到每次破解請求和響應的密文,尋找不同,一旦發現了一個,他會和執行破解的Javascript通訊並繼續破解下一位。

BREACH攻擊是CRIME攻擊的升級版,攻擊方法和CRIME相同,不同的是BREACH利用的不是SSL/TLS壓縮,而是HTTP壓縮。所以要抵禦BREACH攻擊必須禁用HTTP壓縮。

  • BEAST攻擊

TLS 在 1.1版本之前,下一個記錄的IV是直接使用的前一個記錄的密文。BEAST攻擊就是利用了這一點,攻擊者控制受害者傳送大量請求,利用可預測性IV猜測關鍵資訊。解決方法就是部署TLS 1.1或者更高階的版本。

  • RC4攻擊

基於RC4演算法的安全性,RC4目前已經不安全,應該禁用。

  • POODLE攻擊

是SSL 3.0設計上的漏洞,使用了非確定性的CBC-padding,使中間人攻擊者更容易通過padding-oracle攻擊獲取明文資料。

  • 降級攻擊(版本回退攻擊)

欺騙伺服器使用低版本的不安全的TLS協議,常和其他攻擊手段結合使用。刪除後向相容性通常是防止降低攻擊的唯一方法。

前向安全性

如果沒有前向安全性,一旦私鑰洩漏,不僅將來的會話會受影響,過去的會話也都會受影響。一個耐心的黑客,可以先把以前截獲的資料先儲存下來,一旦私鑰洩露或被破解,就可以破解之前的所有密文。這就是所謂的今日截獲,明日破解

TLS的實現之一就是通過使用臨時的DH金鑰交換來生成會話金鑰,一次一密保證了即使黑客花大力氣破解了這一次的會話金鑰,也只是這次通訊被攻擊,之前的歷史訊息不會受到影響。這我們已經在第一部分中講到了。

但即使是使用了臨時DH金鑰交換,服務端的session管理機制也會影響到前向安全性。前面Session重用的部分我們講到了session ticket,它的保護完全是依賴於對稱加密,所以長時間有效的session ticket金鑰會阻止前向安全性的實現。

在實踐中,應該優先使用臨時DH金鑰交換類演算法套件,session的有效期不宜設定過長,session ticket的key也應該經常更換。

TLS 1.3新特性

TLS 1.3相比TLS 1.2改動巨大,它的主要改進目標是最大相容強化安全提升效能

下面列舉了相比TLS 1.3的主要差異

  • 對稱加密演算法只保留了AEAD類演算法,將金鑰交換和認證演算法從演算法套件的概念中分離出去了
  • 增加了0-RTT模式
  • 移除了靜態RSA和DH金鑰協商(現在所有基於公鑰的金鑰交換都提供了前向安全性)
  • 現在所有的ServerHello後的握手訊息都是加密的
  • 金鑰派生函式重新設計,KDF換成了標準的HKDF
  • 握手狀態機大幅重構,砍掉了多餘的訊息如ChangeCipherSpec
  • 使用統一的PSK模型,替代了之前的Session Resumption(包括Session ID和Session Ticket)及早期TLS版本基於PSK(rfc4279)的演算法套件

這裡介紹幾個關鍵的特性

金鑰交換模式

TLS 1.3提出了3種金鑰交換模式:

  • (EC)DHE
  • PSK-only(pre-shared symmetric key)
  • PSK with (EC)DHE 前兩者的結合,具有前向安全性

1-RTT握手

如前所述,TLS 1.2的完整握手有2個RTT,第一個RTT是ClientHello/ServerHello,第二個RTT是ServerKeyExchange/ClientKeyExchange。之所以需要兩個RTT是因為TLS 1.2支援多種金鑰交換演算法及各種不同的引數,這些都依賴第一個RTT去協商出來。TLS 1.3直接大刀闊斧,砍掉了各種自定義的group、curve,砍掉了RSA金鑰交換,只剩下為數不多的幾個金鑰交換演算法,實際應用中大部分使用ECDH P-256X25519。所以乾脆讓客戶端快取伺服器上一次用的是啥金鑰交換演算法,把KeyExchange直接合入第一個RTT。如果伺服器發現客戶端發上來的演算法不對,那麼再告訴它正確的,讓客戶端重試就好了。(這就引入了HelloRetryRequest訊息)。這樣基本沒有副作用,就可以降到1-RTT了。

TLS 1.3的完整握手流程如下:

       Client                                           Server

Key  ^ ClientHello
Exch | + key_share*
| + signature_algorithms*
| + psk_key_exchange_modes*
v + pre_shared_key* -------->
ServerHello ^ Key
+ key_share* | Exch
+ pre_shared_key* v
{EncryptedExtensions} ^ Server
{CertificateRequest*} v Params
{Certificate*} ^
{CertificateVerify*} | Auth
{Finished} v
<-------- [Application Data*]
^ {Certificate*}
Auth | {CertificateVerify*}
v {Finished} -------->
[Application Data] <-------> [Application Data] + Indicates noteworthy extensions sent in the
previously noted message. * Indicates optional or situation-dependent
messages/extensions that are not always sent. {} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret. [] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N. Figure 1: Message Flow for Full TLS Handshake

握手過程可以分成三個階段:

  • 金鑰交換:建立共享金鑰材料,選擇加密引數。此階段後的所有資訊都是加密的。
  • 服務端引數:建立其他握手引數,如客戶端是否需要認證、應用層協議支援
  • 認證:身份認證,提供金鑰確認和握手完整性

重用和PSK

TLS的PSK可以直接在帶外建立,也可以通過前一個連線的會話建立。一旦一個握手完成了之後,服務端就會給客戶端傳送一個PSK id,對應初始握手推匯出的金鑰。(對應TLS 1.2及之前版本的Session ID和Session Tickets,這兩個機制在TLS 1.3中都棄用了)。

PSK可以單獨使用,或者跟(EC)DHE金鑰交換結合使用以提供前向安全性。

重用和PSK的握手流程如下:

          Client                                               Server

   Initial Handshake:
ClientHello
+ key_share -------->
ServerHello
+ key_share
{EncryptedExtensions}
{CertificateRequest*}
{Certificate*}
{CertificateVerify*}
{Finished}
<-------- [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished} -------->
<-------- [NewSessionTicket]
[Application Data] <-------> [Application Data] Subsequent Handshake:
ClientHello
+ key_share*
+ pre_shared_key -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
{Finished}
<-------- [Application Data*]
{Finished} -------->
[Application Data] <-------> [Application Data] Figure 3: Message Flow for Resumption and PSK

這種情況下服務端的身份通過PSK認證,所以服務端不傳送CertficateCertificateVerify訊息。當客戶端通過PSK提議重用的時候,也應該提供key_share擴充套件,以便服務端在拒絕重用的時候回退到全握手。

有副作用的0-RTT握手

當客戶端和服務端共享一個PSK的時候(無論是通過外部獲得還是通過前面的握手獲得),TLS 1.3允許客戶端在第一個flight上就傳送資料(early data)。客戶端使用PSK來認證服務端及加密early data。

0-RTT的握手流程如下,與PSK重用的1-RTT握手相比,增加early_data擴充套件以及第一個flight上的0-RTT應用資料。當接收到服務端的Finished訊息之後,會發送一個EndOfEarlyData訊息指示後面加密金鑰的更換。

         Client                                               Server

         ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
+ early_data*
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data] + Indicates noteworthy extensions sent in the
previously noted message. * Indicates optional or situation-dependent
messages/extensions that are not always sent. () Indicates messages protected using keys
derived from a client_early_traffic_secret. {} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret. [] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N. Figure 4: Message Flow for a 0-RTT Handshake

0-RTT的資料安全性較弱:

  • 0-RTT資料沒有前向安全性,因為其加密金鑰單純是從PSK推匯出來的
  • 跨連線可以重放0-RTT裡的應用資料(常規的TLS 1.3 1-RTT資料通過服務端隨機數來防重放)

金鑰派生過程

金鑰派生過程用到了HKDF-Extract和HKDF-Expand函式,以及如下的函式

HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret, HkdfLabel, Length)

其中HkdfLabel表示

struct {
uint16 length = Length;
opaque label<7..255> = "tls13 " + Label;
opaque context<0..255> = Context;
} HkdfLabel; Derive-Secret(Secret, Label, Messages) = HKDF-Expand-Label(
Secret, Label, Transcript-Hash(Messages), Hash.length)

不管是哪種金鑰交換模式都給走完下面的整個流程,當沒有對應的輸入金鑰材料(IKM),對應的位置用Hash長度的0值字串代替。例如沒有PSK的話,Early Secret就是HKDF-Extract(0, 0)

其中exporter_secret是匯出金鑰,用於使用者自定義的其他用途。resumption_master_secret用於生成ticket。client_early_traffic_secret用於推導0-RTT的early-data金鑰,*_handshake_traffic_secret用於推到握手訊息的加密金鑰,*_application_traffic_secret_N用於推導應用訊息的加密金鑰。

常見實現

OpenSSL:非常流行的開源實現、程式碼量最大、寫得最爛?

LibreSSL:也是OpenSSL的一個fork,OpenBSD專案

BoringSSL:是OpenSSL的一個fork分支,主要用於Google的Chrome/Chromium、Android以及其他應用

JSSE(Java Secure Socket Extension):Java實現

NSS:最初由網景開發的庫,現在主要被瀏覽器和客戶端軟體使用,比如Firefox使用的就是NSS庫(Mozilla開發)。

go.crypto:Go語言的實現

參考資料