1. TLS 定義
SSL(Secure Sockets Layer) 安全套接層,是一種安全協議,經歷了 SSL 1.0、2.0、3.0 版本後發展成了標準安全協議 - TLS(Transport Layer Security) 傳輸層安全性協議。TLS 有 1.0 (RFC 2246)、1.1(RFC 4346)、1.2(RFC 5246)、1.3(RFC 8446) 版本。
TLS 在實現上分為 記錄層 和 握手層 兩層,其中握手層又含四個子協議: 握手協議 (handshake protocol)、更改加密規範協議 (change cipher spec protocol)、應用資料協議 (application data protocol) 和警告協議 (alert protocol)
2. HTTPS = HTTP over TLS.
只需配置瀏覽器和伺服器相關設定開啟 TLS,即可實現 HTTPS,TLS 高度解耦,可裝可卸,與上層高階應用層協議相互協作又相互獨立。
3. 加密
TLS/SSL 的功能實現主要依賴於三類基本演算法:雜湊函式 Hash、對稱加密和非對稱加密,其利用非對稱加密實現身份認證和金鑰協商,對稱加密演算法採用協商的金鑰對資料加密,基於雜湊函式驗證資訊的完整性。
TLS 的基本工作方式是,客戶端使用非對稱加密與伺服器進行通訊,實現身份驗證並協商對稱加密使用的金鑰,然後對稱加密演算法採用協商金鑰對資訊以及資訊摘要進行加密通訊,不同的節點之間採用的對稱金鑰不同,從而可以保證資訊只能通訊雙方獲取。
例如,在 HTTPS 協議中,客戶端發出請求,服務端會將公鑰發給客戶端,客戶端驗證過後生成一個金鑰再用公鑰加密後傳送給服務端(非對稱加密),雙方會在 TLS 握手過程中生成一個協商金鑰(對稱金鑰),成功後建立加密連線。通訊過程中客戶端將請求資料用協商金鑰加密後傳送,服務端也用協商金鑰解密,響應也用相同的協商金鑰。後續的通訊使用對稱加密是因為對稱加解密快,而握手過程中非對稱加密可以保證加密的有效性,但是過程複雜,計算量相對來說也大。
4. 記錄層
記錄協議負責在傳輸連線上交換的所有底層訊息,並且可以配置加密。每一條 TLS 記錄以一個短標頭開始。標頭包含記錄內容的型別 (或子協議)、協議版本和長度。原始訊息經過分段 (或者合併)、壓縮、新增認證碼、加密轉為 TLS 記錄的資料部分。
分片 (Fragmentation)
記錄層將資訊塊分割成攜帶 2^14 位元組 (16KB) 或更小塊的資料的 TLSPlaintext 記錄。
記錄協議傳輸由其他協議層提交給它的不透明資料緩衝區。如果緩衝區超過記錄的長度限制(2^14),記錄協議會將其切分成更小的片段。反過來也是可能的,屬於同一個子協議的小緩衝區也可以組合成一個單獨的記錄。
struct {
uint8 major, minor;
} ProtocolVersion;
enum {
change_cipher_spec(20),
alert(21),
handshake(22),
application_data(23), (255)
} ContentType;
struct {
ContentType type; // 用於處理封閉片段的較高階協議
ProtocolVersion version; // 使用的安全協議版本
uint16 length; // TLSPlaintext.fragment 的長度(以位元組為單位),不超過 2^14
opaque fragment[TLSPlaintext.length]; // 透明的應用資料,被視為獨立的塊,由型別欄位指定的較高階協議處理
} TLSPlaintext;
複製程式碼
記錄壓縮和解壓縮 (Record compression and decompression)
壓縮演算法將 TLSPlaintext 結構轉換為 TLSCompressed 結構。如果定義 CompressionMethod 為 null 表示不壓縮
struct {
ContentType type; // same as TLSPlaintext.type
ProtocolVersion version; // same as TLSPlaintext.version
uint16 length; // TLSCompressed.fragment 的長度,不超過 2^14 + 1024
opaque fragment[TLSCompressed.length];
} TLSCompressed;
複製程式碼
空或標準流加密 (Null or standard stream cipher)
流加密(BulkCipherAlgorithm)將 TLSCompressed.fragment 結構轉換為流 TLSCiphertext.fragment 結構
stream-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[CipherSpec.hash_size];
} GenericStreamCipher;
複製程式碼
MAC 產生方法如下:
HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment));
複製程式碼
seq_num(記錄的序列號)、hash(SecurityParameters.mac_algorithm 指定的雜湊演算法)
MAC(Message authentication code) - 訊息認證碼
注意,MAC 是在加密之前計算的。流加密加密整個塊,包括 MAC。對於不使用同步向量 (例如 RC4) 的流加密,從一個記錄結尾處的流加密狀態僅用於後續資料包。如果 CipherSuite 是 TLS_NULL_WITH_NULL_NULL,則加密由身份操作 (資料未加密,MAC 大小為零,暗示不使用 MAC) 組成。TLSCiphertext.length 是 TLSCompressed.length 加上 CipherSpec.hash_size。
CBC 塊加密 (分組加密)
塊加密(如 RC2 或 DES),將 TLSCompressed.fragment 結構轉換為塊 TLSCiphertext.fragment 結構
block-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[CipherSpec.hash_size];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
} GenericBlockCipher;
複製程式碼
padding: 新增的填充將明文長度強制為塊密碼塊長度的整數倍。填充可以是長達 255 位元組的任何長度,只要滿足 TLSCiphertext.length 是塊長度的整數倍。長度大於需要的值可以阻止基於分析交換資訊長度的協議攻擊。填充資料向量中的每個 uint8 必須填入填充長度值 (即 padding_length)。
padding_length: 填充長度應該使得 GenericBlockCipher 結構的總大小是加密塊長度的倍數。合法值範圍從零到 255(含)。該長度指定 padding_length 欄位本身除外的填充欄位的長度
加密塊的資料長度(TLSCiphertext.length)是 TLSCompressed.length,CipherSpec.hash_size 和 padding_length 的總和加一
示例: 如果塊長度為 8 位元組,壓縮內容長度(TLSCompressed.length)為 61 位元組,MAC 長度為 20 位元組,則填充前的長度為 82 位元組(padding_length 佔 1 位元組)。 因此,為了使總長度為塊長度 (8 位元組) 的偶數倍,模 8 的填充長度必須等於 6,所以填充長度可以為 6,14,22 等。如果填充長度是需要的最小值,比如 6,填充將為 6 位元組,每個塊都包含值 6。因此,塊加密之前的 GenericBlockCipher 的最後 8 個八位位元組將為 xx 06 06 06 06 06 06 06,其中 xx 是 MAC 的最後一個八位位元組。
XX - 06 06 06 06 06 06 - 06 MAC - padding[6] - padding_length 複製程式碼
記錄有效載荷保護 (Record payload protection)
加密和 MAC 功能將 TLSCompressed 結構轉換為 TLSCiphertext。記錄的 MAC 還包括序列號,以便可以檢測到丟失,額外或重複的訊息。
struct {
ContentType type; // same
ProtocolVersion version; // same
uint16 length; // TLSCiphertext.fragment 的長度,不超過 2^14 + 2048
select (CipherSpec.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
} fragment; // TLSCompressed.fragment 的加密形式,帶有 MAC
} TLSCiphertext;
複製程式碼
注意 這裡提到的都是先 MAC 再加密,是基於 RFC 2246 的方案 (TLS 1.0) 寫的。但新的方案選擇先加密再 MAC,這種替代方案中,首先對明文和填充進行加密,再將結果交給 MAC 演算法。這可以保證主動網路攻擊者不能操縱任何加密資料。
金鑰計算 (Key calculation)
記錄協議需要一種演算法,從握手協議提供的安全性引數生成金鑰、IV 和 MAC secret.
主金鑰 (Master secret): 在連線中雙方共享的一個 48 位元組的金鑰 客戶隨機數 (client random): 由客戶端提供的 32 位元組值 伺服器隨機數 (server random): 由伺服器提供的 32 位元組值
5. 握手層
- 握手協議的職責是生成通訊過程所需的共享金鑰和進行身份認證。這部分使用無密碼套件,為防止資料被竊聽,通過公鑰密碼或 Diffie-Hellman 金鑰交換技術通訊。
- 密碼規格變更協議,用於密碼切換的同步,是在握手協議之後的協議。握手協議過程中使用的協議是“不加密”這一密碼套件,握手協議完成後則使用協商好的密碼套件。
- 警告協議,當發生錯誤時使用該協議通知通訊對方,如握手過程中發生異常、訊息認證碼錯誤、資料無法解壓縮等。
- 應用資料協議,通訊雙方真正進行應用資料傳輸的協議,傳送過程通過 TLS 應用資料協議和 TLS 記錄協議來進行傳輸。
握手是 TLS 協議中最精密複雜的部分。在這個過程中,通訊雙方協商連線引數,並且完成身 份驗證。根據使用的功能的不同,整個過程通常需要交換 6~10 條訊息。根據配置和支援的協議擴充套件的不同,交換過程可能有許多變種。在使用中經常可以觀察到以下三種流程:(1) 完整的握手, 對伺服器進行身份驗證;(2) 恢復之前的會話採用的簡短握手;(3) 對客戶端和伺服器都進行身份驗證的握手。
握手協議訊息的標頭資訊包含訊息型別(1 位元組)和長度(3 位元組),餘下的資訊則取決於訊息型別:
struct {
HandshakeType msg_type;
uint24 length;
HandshakeMessage message;
} Handshake;
複製程式碼
5.1 完整握手
每一個 TLS 連線都會以握手開始。如果客戶端此前並未與伺服器建立會話,那麼雙方會執行一次完整的握手流程來協商 TLS 會話。握手過程中,客戶端和伺服器將進行以下四個主要步驟:
- 交換各自支援的功能,對需要的連線引數達成一致
- 驗證出示的證書,或使用其他方式進行身份驗證
- 對將用於保護會話的共享主金鑰達成一致
- 驗證握手訊息並未被第三方團體修改
下面介紹最常見的握手規則,一種不需要驗證客戶端身份但需要驗證伺服器身份的握手:
5.1.1 ClientHello
這條訊息將客戶端的功能和首選項傳送給伺服器。
- Version: 協議版本(protocol version)指示客戶端支援的最佳協議版本
- Random: 一個 32 位元組資料,28 位元組是隨機生成的 (圖中的 Random Bytes);剩餘的 4 位元組包含額外的資訊,與客戶端時鐘有關 (圖中使用的是 GMT Unix Time)。在握手時,客戶端和伺服器都會提供隨機數,客戶端的暫記作 random_C (用於後續的金鑰的生成)。這種隨機性對每次握手都是獨一無二的,在身份驗證中起著舉足輕重的作用。它可以防止 重放攻擊,並確認初始資料交換的完整性。
- Session ID: 在第一次連線時,會話 ID(session ID)欄位是空的,這表示客戶端並不希望恢復某個已存在的會話。典型的會話 ID 包含 32 位元組隨機生成的資料,一般由服務端生成通過 ServerHello 返回給客戶端。
- Cipher Suites: 密碼套件(cipher suite)塊是由客戶端支援的所有密碼套件組成的列表,該列表是按優先順序順序排列的
- Compression: 客戶端可以提交一個或多個支援壓縮的方法。預設的壓縮方法是 null,代表沒有壓縮
- Extensions: 擴充套件(extension)塊由任意數量的擴充套件組成。這些擴充套件會攜帶額外資料
5.1.2 ServerHello
是將伺服器選擇的連線引數傳回客戶端。
這個訊息的結構與 ClientHello 類似,只是每個欄位只包含一個選項,其中包含服務端的 random_S 引數 (用於後續的金鑰協商)。伺服器無需支援客戶端支援的最佳版本。如果伺服器不支援與客戶端相同的版本,可以提供某個其他版本以期待客戶端能夠接受
圖中的 Cipher Suite
是後續金鑰協商和身份驗證要用的加密套件,此處選擇的金鑰交換與簽名演算法是 ECDHE_RSA,對稱加密演算法是 AES-GCM,後面會講到這個
還有一點預設情況下 TLS 壓縮都是關閉的,因為 CRIME 攻擊會利用 TLS 壓縮恢復加密認證 cookie,實現會話劫持,而且一般配置 gzip 等內容壓縮後再壓縮 TLS 分片效益不大又額外佔用資源,所以一般都關閉 TLS 壓縮
5.1.3 Certificate
典型的 Certificate 訊息用於攜帶伺服器 X.509 證書鏈。 伺服器必須保證它傳送的證書與選擇的演算法套件一致。比方說,公鑰演算法與套件中使用的必須匹配。除此以外,一些金鑰交換演算法依賴嵌入證書的特定資料,而且要求證書必須以客戶端支援的演算法簽名。所有這些都表明伺服器需要配置多個證書(每個證書可能會配備不同的證書鏈)。
Certificate 訊息是可選的,因為並非所有套件都使用身份驗證,也並非所有身份驗證方法都需要證書。更進一步說,雖然訊息預設使用 X.509 證書,但是也可以攜帶其他形式的標誌;一些套件就依賴 PGP 金鑰
5.1.4 ServerKeyExchange
攜帶金鑰交換需要的額外資料。ServerKeyExchange 是可選的,訊息內容對於不同的協商演算法套件會存在差異。部分場景下,比如使用 RSA 演算法時,伺服器不需要傳送此訊息。
ServerKeyExchange 僅在伺服器證書訊息(也就是上述 Certificate 訊息)不包含足夠的資料以允許客戶端交換預主金鑰(premaster secret)時才由伺服器傳送。
比如基於 DH 演算法的握手過程中,需要單獨傳送一條 ServerKeyExchange 訊息帶上 DH 引數:
5.1.5 ServerHelloDone
表明伺服器已經將所有預計的握手訊息傳送完畢。在此之後,伺服器會等待客戶端傳送訊息。
5.1.6 verify certificate
客戶端驗證證書的合法性,如果驗證通過才會進行後續通訊,否則根據錯誤情況不同做出提示和操作,合法性驗證內容包括如下:
- 證書鏈的可信性 trusted certificate path;
- 證書是否吊銷 revocation,有兩類方式 - 離線 CRL 與線上 OCSP,不同的客戶端行為會不同;
- 有效期 expiry date,證書是否在有效時間範圍;
- 域名 domain,核查證書域名是否與當前的訪問域名匹配;
由 PKI 體系
的內容可知,對端發來的證書籤名是 CA 私鑰加密的,接收到證書後,先讀取證書中的相關的明文資訊,採用相同的雜湊函式計算得到資訊摘要,然後利用對應 CA 的公鑰解密簽名資料,對比證書的資訊摘要,如果一致,則可以確認證書的合法性;然後去查詢證書的吊銷情況等
5.1.7 ClientKeyExchange
合法性驗證通過之後,客戶端計算產生隨機數字的預主金鑰(Pre-master),並用證書公鑰加密,傳送給伺服器並攜帶客戶端為金鑰交換提供的所有資訊。這個訊息受協商的密碼套件的影響,內容隨著不同的協商密碼套件而不同。
此時客戶端已經獲取全部的計算協商金鑰需要的資訊: 兩個明文隨機數 random_C 和 random_S 與自己計算產生的 Pre-master,然後得到協商金鑰(用於之後的訊息加密)
enc_key = PRF(Pre_master, "master secret", random_C + random_S)
複製程式碼
圖中使用的是 ECDHE 演算法,ClientKeyExchange 傳遞的是 DH 演算法的客戶端引數,如果使用的是 RSA 演算法則此處應該傳遞加密的預主金鑰
5.1.8 ChangeCipherSpec
通知伺服器後續的通訊都採用協商的通訊金鑰和加密演算法進行加密通訊
注意 ChangeCipherSpec 不屬於握手訊息,它是另一種協議,只有一條訊息,作為它的子協議進行實現。
5.1.9 Finished (Encrypted Handshake Message)
Finished 訊息意味著握手已經完成。訊息內容將加密,以便雙方可以安全地交換驗證整個握手完整性所需的資料。
這個訊息包含 verify_data 欄位,它的值是握手過程中所有訊息的雜湊值。這些訊息在連線兩端都按照各自所見的順序排列,並以協商得到的主金鑰 (enc_key) 計算雜湊。這個過程是通過一個偽隨機函式(pseudorandom function,PRF)來完成的,這個函式可以生成任意數量的偽隨機資料。 兩端的計算方法一致,但會使用不同的標籤(finished_label):客戶端使用 client finished,而伺服器則使用 server finished。
verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))
複製程式碼
因為 Finished 訊息是加密的,並且它們的完整性由協商 MAC 演算法保證,所以主動網路攻擊者不能改變握手訊息並對 vertify_data 的值造假。在 TLS 1.2 版本中,Finished 訊息的長度預設是 12 位元組(96 位),並且允許密碼套件使用更長的長度。在此之前的版本,除了 SSL 3 使用 36 位元組的定長訊息,其他版本都使用 12 位元組的定長訊息。
5.1.10 Server
伺服器用私鑰解密加密的 Pre-master 資料,基於之前交換的兩個明文隨機數 random_C 和 random_S,同樣計算得到協商金鑰: enc_key = PRF(Pre_master, "master secret", random_C + random_S)
;
同樣計算之前所有收發資訊的 hash 值,然後用協商金鑰解密客戶端傳送的 verify_data_C,驗證訊息正確性;
5.1.11 change_cipher_spec
服務端驗證通過之後,伺服器同樣傳送 change_cipher_spec 以告知客戶端後續的通訊都採用協商的金鑰與演算法進行加密通訊(圖中多了一步 New Session Ticket,此為會話票證,會在會話恢復中解釋);
5.1.12 Finished (Encrypted Handshake Message)
伺服器也結合所有當前的通訊引數資訊生成一段資料 (verify_data_S) 並採用協商金鑰 session secret (enc_key) 與演算法加密併傳送到客戶端;
5.1.13 握手結束
客戶端計算所有接收資訊的 hash 值,並採用協商金鑰解密 verify_data_S,驗證伺服器傳送的資料和金鑰,驗證通過則握手完成;
5.1.14 加密通訊
開始使用協商金鑰與演算法進行加密通訊。
5.2 金鑰交換和簽名演算法
常用的金鑰交換和簽名演算法
HTTPS 通過 TLS 層和證書機制提供了內容加密、身份認證和資料完整性三大功能。加密過程中,需要用到非對稱金鑰交換和對稱內容加密兩大演算法。
對稱內容加密強度非常高,加解密速度也很快,只是無法安全地生成和保管金鑰。在 TLS 協議中,最後的應用資料都是經過對稱加密後傳輸的,傳輸中所使用的對稱協商金鑰(上文中的 enc_key),則是在握手階段通過非對稱金鑰交換而來。常見的 AES-GCM、ChaCha20-Poly1305,都是對稱加密演算法。
非對稱金鑰交換能在不安全的資料通道中,產生只有通訊雙方才知道的對稱加密金鑰。目前最常用的金鑰交換演算法有 RSA 和 ECDHE。
RSA 歷史悠久,支援度好,但不支援 完美前向安全 - PFS(Perfect Forward Secrecy);而 ECDHE 是使用了 ECC(橢圓曲線)的 DH(Diffie-Hellman)演算法,計算速度快,且支援 PFS。
在 PKI 體系
一節中說明了僅有非對稱金鑰交換還是無法抵禦 MITM 攻擊的,所以需要引入了 PKI 體系的證書來進行身份驗證,其中服務端非對稱加密產生的公鑰會放在證書中傳給客戶端。
在 RSA 金鑰交換中,瀏覽器使用證書提供的 RSA 公鑰加密相關資訊,如果服務端能解密,意味著服務端擁有與公鑰對應的私鑰,同時也能算出對稱加密所需金鑰。金鑰交換和服務端認證合併在一起。
在 ECDH 金鑰交換中,服務端使用私鑰 (RSA 或 ECDSA) 對相關資訊進行簽名,如果瀏覽器能用證書公鑰驗證簽名,就說明服務端確實擁有對應私鑰,從而完成了服務端認證。金鑰交換則是各自傳送 DH 引數完成的,金鑰交換和服務端認證是完全分開的。
可用於 ECDHE 數字簽名的演算法主要有 RSA 和 ECDSA - 橢圓曲線數字簽名演算法,也就是目前金鑰交換 + 簽名有三種主流選擇:
RSA
- RSA 金鑰交換(無需簽名)ECDHE_RSA
- ECDHE 金鑰交換、RSA 簽名ECDHE_ECDSA
- ECDHE 金鑰交換、ECDSA 簽名
比如我的網站使用的加密套件是 ECDHE_RSA,可以看到數字簽名演算法是 sha256 雜湊加 RSA 加密,在 PKI 體系
一節中講了簽名是伺服器資訊摘要的雜湊值加密生成的
內建 ECDSA 公鑰的證書一般被稱之為 ECC 證書,內建 RSA 公鑰的證書就是 RSA 證書。因為 256 位 ECC Key 在安全性上等同於 3072 位 RSA Key,所以 ECC 證書體積比 RSA 證書小,而且 ECC 運算速度更快,ECDHE 金鑰交換 + ECDSA 數字簽名是目前最好的加密套件
以上內容來自本文: 開始使用 ECC 證書
關於 ECC 證書的更多細節可見文件: ECC Cipher Suites for TLS - RFC4492
RSA 金鑰交換和 DH 金鑰交換的區別
使用 RSA 進行金鑰交換的握手過程與前面說明的基本一致,只是沒有 ServerKeyExchange 訊息,其中協商金鑰涉及到三個引數 (客戶端隨機數 random_C、服務端隨機數 random_S、預主金鑰 Premaster secret), 其中前兩個隨機數和協商使用的演算法是明文的很容易獲取,最後一個 Premaster secret 會用伺服器提供的公鑰加密後傳輸給伺服器 (金鑰交換),如果這個預主金鑰被擷取並破解則協商金鑰也可以被破解。
RSA 演算法的細節見: wiki 和 RSA演算法原理(二)- 阮一峰
RSA 的演算法核心思想是利用了極大整數 因數分解 的計算複雜性
而使用 DH(Diffie-Hellman) 演算法 進行金鑰交換,雙方只要交換各自的 DH 引數(在 ServerKeyExchange 傳送 Server params,在 ClientKeyExchange 傳送 Client params),不需要傳遞 Premaster secret,就可以各自算出這個預主金鑰
DH 的握手過程如下,大致過程與 RSA 類似,圖中只表達如何生成預主金鑰:
伺服器通過私鑰將客戶端隨機數 random_C,服務端隨機數 random_S,服務端 DH 引數 Server params 簽名生成 signature,然後在 ServerKeyExchange 訊息中傳送服務端 DH 引數和該簽名;
客戶端收到後用伺服器給的公鑰解密驗證簽名,並在 ClientKeyExchange 訊息中傳送客戶端 DH 引數 Client params;
服務端收到後,雙方都有這兩個引數,再各自使用這兩個引數生成預主金鑰 Premaster secret,之後的協商金鑰等步驟與 RSA 基本一致。
基於 RSA 演算法與 DH 演算法的握手最大的區別就在於金鑰交換與身份認證。前者客戶端使用公鑰加密預主金鑰併傳送給服務端完成金鑰交換,服務端利用私鑰解密完成身份認證。後者利用各自傳送的 DH 引數完成金鑰交換,伺服器私鑰簽名資料,客戶端公鑰驗簽完成身份認證。
關於 DH 演算法如何生成預主金鑰,推薦看下 Wiki 和 Ephemeral Diffie-Hellman handshake
其核心思想是利用了 離散對數問題 的計算複雜性
原根:假設一個整數 g 對於質數 P 來說是原根,那麼 g^i mod P (1 ≦ i < P) 的結果各不相同,且其結果按一定順序排列後是 1 到 P-1 的所有整數,例子
離散對數:如果對於一個整數 n 和質數 P 的一個原根 g,可以找到一個唯一的指數 i,使得 n = g^i mod P (0 ≦ i < P),那麼指數 i 稱為 n 的以 g 為基數的模 P 的離散對數
Diffie-Hellman 演算法的有效性依賴於計算離散對數的難度,其含義是:當已知大素數 P 和它的一個原根 g 後,對給定的 n,要計算 i,被認為是很困難的,而給定 i 計算 n 卻相對容易
演算法過程可以抽象成下圖:
雙方預先商定好了一對 P g 值 (公開的),而 Alice 有一個私密數 a(非公開,對應一個私鑰),Bob 有一個私密數 b(非公開,對應一個私鑰)
-
Alice 計算 A = g^a mod P,並把 A(公開,對應一個公鑰) 發給 Bob
-
Bob 計算 B = g^b mod P,並把 B(公開,對應一個公鑰) 發給 Alice
-
雙方計算出共享金鑰,K = B^a mod P = A^b mod P (= g^ab mod P)
對於 Alice 和 Bob 來說通過對方發過來的公鑰引數和自己手中的私鑰可以得到最終相同的金鑰
而第三方最多知道 P g A B,想得到私鑰和最後的金鑰很困難,當然前提是 a b P 足夠大 (RFC3526 文件中有幾個常用的大素數可供使用),否則暴力破解也有可能試出答案,至於 g 一般取個較小值就可以
如下幾張圖是實際 DH 握手傳送的內容:
可以看到雙方發給對方的引數中攜帶了一個公鑰值,對應上述的 A 和 B
而且實際用的加密套件是 橢圓曲線 DH 金鑰交換 (ECDH),利用由橢圓曲線加密建立公鑰與私鑰對可以更進一步加強 DH 的安全性,因為目前解決橢圓曲線離散對數問題要比因式分解困難的多,而且 ECC 使用的金鑰長度比 RSA 金鑰短得多(目前 RSA 金鑰需要 2048 位以上才能保證安全,而 ECC 金鑰 256 位就足夠)
關於 橢圓曲線密碼學 - ECC,推薦看下 A Primer on Elliptic Curve Cryptography - 原文 - 譯文
5.3 客戶端身份驗證
儘管可以選擇對任意一端進行身份驗證,但人們幾乎都啟用了對伺服器的身份驗證。如果伺服器選擇的套件不是匿名的,那麼就需要在 Certificate 訊息中跟上自己的證書。
相比之下,伺服器通過傳送 CertificateRequest 訊息請求對客戶端進行身份驗證。訊息中列出所有可接受的客戶端證書。作為響應,客戶端傳送自己的 Certificate 訊息(使用與伺服器傳送證書相同的格式),並附上證書。此後,客戶端傳送 CertificateVerify 訊息,證明自己擁有對應的私鑰。
只有已經過身份驗證的伺服器才被允許請求客戶端身份驗證。基於這個原因,這個選項也被稱為相互身份驗證(mutual authentication)。
5.3.1 CertificateRequest
在 ServerHello 的過程中發出,請求對客戶端進行身份驗證,並將其接受的證書的公鑰和簽名演算法傳送給客戶端。
它也可以選擇傳送一份自己接受的證書頒發機構列表,這些機構都用其可分辨名稱來表示:
struct {
ClientCertificateType certificate_types;
SignatureAndHashAlgorithm supported_signature_algorithms;
DistinguishedName certificate_authorities;
} CertificateRequest;
複製程式碼
5.3.2 CertificateVerify
在 ClientKeyExchange 的過程中發出,證明自己擁有的私鑰與之前傳送的客戶端證書中的公鑰匹配。訊息中包含一條到這一步為止的所有握手訊息的簽名:
struct {
Signature handshake_messages_signature;
} CertificateVerify;
複製程式碼
5.4 會話恢復
最初的會話恢復機制是,在一次完整協商的連線斷開時,客戶端和伺服器都會將會話的安全引數儲存一段時間。希望使用會話恢復的伺服器為會話指定唯一的標識,稱為會話 ID(Session ID)。伺服器在 ServerHello 訊息中將會話 ID 發回客戶端。
希望恢復早先會話的客戶端將適當的 Session ID 放入 ClientHello 訊息,然後提交。伺服器如果同意恢復會話,就將相同的 Session ID 放入 ServerHello 訊息返回,接著使用之前協商的主金鑰生成一套新的金鑰,再切換到加密模式,傳送 Finished 訊息。 客戶端收到會話已恢復的訊息以後,也進行相同的操作。這樣的結果是握手只需要一次網路往返。
Session ID 由伺服器端支援,協議中的標準欄位,因此基本所有伺服器都支援,伺服器端儲存會話 ID 以及協商的通訊資訊,佔用伺服器資源較多。
用來替代伺服器會話快取和恢復的方案是使用會話票證(Session ticket)。使用這種方式,除了所有的狀態都儲存在客戶端(與 HTTP Cookie 的原理類似)之外,其訊息流與伺服器會話快取是一樣的。
其思想是伺服器取出它的所有會話資料(狀態)並進行加密 (金鑰只有伺服器知道),再以票證的方式發回客戶端。在接下來的連線中,客戶端恢復會話時在 ClientHello 的擴充套件欄位 session_ticket 中攜帶加密資訊將票證提交回伺服器,由伺服器檢查票證的完整性,解密其內容,再使用其中的資訊恢復會話。
這種方法有可能使擴充套件伺服器叢集更為簡單,因為如果不使用這種方式,就需要在服務叢集的各個節點之間同步會話。 Session ticket 需要伺服器和客戶端都支援,屬於一個擴充套件欄位,佔用伺服器資源很少。
警告 會話票證破壞了 TLS 安全模型。它使用票證金鑰加密的會話狀態並將其暴露線上路上。有些實現中的票證金鑰可能會比連線使用的密碼要弱。如果票證金鑰被暴露,就可以解密連線上的全部資料。因此,使用會話票證時,票證金鑰需要頻繁輪換。
References
- RFC 2246 - The TLS Protocol Version 1.0
- NPN/ALPN - wiki
- TLS - wiki
- SSL/TLS in detail
- HTTPS 加密協議詳解
- Keyless SSL: The Nitty Gritty Technical Details
- DSA - 數字簽名演算法
- 《HTTPS 權威指南 - 在伺服器和 web 應用上部署 SSL/TLS 和 PKI》