參考文章:https://blog.csdn.net/alwaysrun/article/details/89076492
https://www.jianshu.com/p/fd0a624d0912
https://cloud.tencent.com/developer/article/1928677
文件:https://www.rfc-editor.org/rfc/rfc6347
https://www.rfc-editor.org/rfc/rfc5246
1.SSL/TLS 協議
1.1背景
首先從我們比較熟悉的 SSL/TLS 開始講起。SSL(Secure Socket Layer) 和 TLS(Transport Layer Security) 簡單理解就是同一件東西的兩個演進階段,同樣都是在應用層和傳輸層之間加入的安全層,最早的時候這個安全層叫做 SSL,由 Netscape 公司推出,後來被 IETF 組織標準化並稱之為 TLS。
TLS的動機從HTTPS講起。HTTP 本身不具備加密的功能,所以也無法做到對通訊整體(使用 HTTP 協議通訊的請求和響應的內容)進行加密。按傳輸層 TCP/IP 協議族的工作機制,通訊內容在所有的通訊線路上都有可能遭到窺視。而HTTP 在傳輸資料的過程中,所有的資料都是明文傳輸,非常奔放,自然沒有安全性可言。資料在公網上傳輸,容易被第三方獲取,尤其特別是一些敏感資料,比如使用者密碼和信用卡資訊等,一旦被第三方獲取後果不堪設想。HTTPS 主要用於解決資料安全傳輸的問題,透過加密套件,讓資料在網路傳輸過程中對第三方“不可見”(看見的是無意義的亂碼資訊)。在 HTTPS 中,原有的 HTTP 協議會得到 TLS (安全傳輸層協議) 或其前輩 SSL (安全套接層) 的加密。因此 HTTPS 也常指 HTTP over TLS 或 HTTP over SSL,即HTTPS = HTTP + SSL / TLS。
SSL/TLS 的作用是為了解決網際網路通訊中存在的三種風險:
- 竊聽風險:第三方可以獲知通訊內容;
- 篡改風險:第三方可以修改通訊內容;
- 冒充風險:第三方可以冒充他人身份參與通訊。
SSL/TLS 協議能夠做到以下這幾點,從而解決上述的三種風險:
- 所有資訊透過加密傳播,第三方無法竊聽;
- 具有資料簽名及校驗機制,一旦被篡改,通訊雙方立刻可以發現;
- 具有身份證書,防止其他人冒充。
1.2協議細則
(1)架構
SSL/TLS協議有一個高度模組化的架構,分為很多子協議,如下圖所示。也可以簡單地理解為,TLS協議分為兩層,一層是記錄層,一層是握手層。
- TLS記錄層負責從更高層接收資料並對其進行處理。具體來說,記錄層接收要傳輸的訊息,將資料分成可管理的塊,可選地壓縮資料,應用MAC(訊息認證碼),加密,然後傳輸結果。接收到的資料被解密、驗證、解壓縮、重新組裝,然後傳遞給更高層的客戶端。 另外,為了允許TLS協議的擴充套件,記錄層可以支援額外的記錄內容型別。
- 在TLST握手層,中有三個子協議使用到了記錄: handshake protocol、alert protocol、changecipher spec protocol。
SSL握手協議:包括協商安全引數和密碼套件、伺服器身份認證(客戶端身份認證可選)、金鑰交換;
SSL握手金鑰引數更換協議:一條訊息表明握手協議已經完成;
SSL告警協議:對握手協議中一些異常的錯誤提醒,分為fatal和warning兩個級別,fatal型別的錯誤會直接中斷SSL連結,而warning級別的錯誤SSL連結仍可繼續,只是會給出錯誤警告;
(2)流程
SSL/TLS協議的執行過程被設計為兩階段:握手階段和應用階段。
- 握手階段也稱協商階段,在這一階段,客戶端和伺服器端會認證對方身份(依賴於PKI體系,利用數字證書進行身份認證),並協商通訊中使用的安全引數、密碼套件以及MasterSecret。後續通訊使用的所有金鑰都是透過MasterSecret生成。
- 在握手階段完成後,進入應用階段。在應用階段通訊雙方使用握手階段協商好的金鑰進行安全通訊。
隨機數 |
生成方 |
生成報文 |
含義 |
A |
Client |
ClientHello |
C = pre-master-key master-key = function(A,B,pre-master-key) |
B |
Server |
ServerHello |
|
C |
Client |
—— |
1.3協議實現
(1)記錄層
記錄層將接收到的資料分片成大小不超過2^14位元組的TLSPlaintext記錄。這個分片過程是為了使資料塊更易於管理和傳輸。重要的一點是,客戶端的訊息邊界在記錄層中並不保留,這意味著:
多個相同型別的客戶端訊息可以合併成一個TLSPlaintext記錄。
一個訊息可以被分片成多個記錄。
struct { uint8 major; uint8 minor; } ProtocolVersion; enum { change_cipher_spec(20), alert(21), handshake(22), application_data(23), (255) } ContentType; struct { ContentType type; ProtocolVersion version; uint16 length; opaque fragment[TLSPlaintext.length]; } TLSPlaintext;
(2)握手層
ChangeCipherSpec
更改密碼規範協議(Change Cipher Spec Protocol)用於標誌加密策略的轉換。它透過一個單位元組的訊息來通知對方,後續的通訊將使用新協商的加密演算法和金鑰。
struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
Alert
警報訊息傳達訊息的嚴重性(警告或致命)和警報的描述。嚴重性為致命的警報訊息會立即終止連線。
enum { warning(1), fatal(2), (255) } AlertLevel;
//warning:警告級別的警報,表示可能存在問題,但不需要立即終止連線。
//fatal:致命級別的警報,表示嚴重問題,必須立即終止連線。
enum {
close_notify(0),
unexpected_message(10),
bad_record_mac(20),
decryption_failed_RESERVED(21),
record_overflow(22),
decompression_failure(30),
handshake_failure(40),
no_certificate_RESERVED(41),
bad_certificate(42),
unsupported_certificate(43),
certificate_revoked(44),
certificate_expired(45),
certificate_unknown(46),
illegal_parameter(47),
unknown_ca(48),
access_denied(49),
decode_error(50),
decrypt_error(51),
export_restriction_RESERVED(60),
protocol_version(70),
insufficient_security(71),
internal_error(80),
user_canceled(90),
no_renegotiation(100),
unsupported_extension(110),
(255)
} AlertDescription;
struct {
AlertLevel level;
AlertDescription description;
} Alert;
Handshake
TLS握手協議的任務是生成會話狀態的加密引數,該協議在TLS記錄層之上執行。當TLS客戶端和伺服器首次開始通訊時,它們會協商協議版本,選擇加密演算法,可選地相互認證,並使用公鑰加密技術生成共享的秘密。
enum {
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange(12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255)
} HandshakeType;
struct {
HandshakeType msg_type; /* 握手型別 */
uint24 length; /* 訊息的位元組數 */
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
①HelloRequest
HelloRequest訊息是由伺服器傳送給客戶端的一個簡單通知,指示客戶端應該重新開始協商過程。
伺服器可以在任何時間傳送HelloRequest訊息,但不應在客戶端初次連線時立即傳送。
HelloRequest訊息結構非常簡單,僅包含一個空的結構體。
struct { } HelloRequest;
②ClientHello
當客戶端首次連線到伺服器時,必須傳送ClientHello作為其第一條訊息。客戶端還可以在收到HelloRequest後或出於自身需要重新協商現有連線的安全引數時傳送ClientHello。
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
enum { null(0), (255) } CompressionMethod;
uint8 CipherSuite[2]; /* 加密套件選擇器 */
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
ClientHello訊息包含以下主要部分:
- Random:一個包含當前時間和28位元組隨機數的結構,用於生成後續的加密引數。
- SessionID:一個可變長度的會話識別符號,用於指示客戶端希望重用的會話。
- CipherSuite:一個加密套件列表,按客戶端的偏好順序排列。
- CompressionMethod:一個壓縮方法列表,按客戶端的偏好順序排列。
- Extensions:一個可選的擴充套件欄位,用於請求伺服器提供擴充套件功能。
加密套件列表包含客戶端支援的加密演算法組合,每個加密套件定義一個金鑰交換演算法、一個批次加密演算法(包括金鑰長度)、一個MAC演算法和一個PRF演算法。
③ClientKeyExchange
此訊息總是由客戶端傳送。如果傳送了客戶端證書訊息,它必須緊跟在客戶端證書訊息之後。否則,它必須是客戶端在接收到ServerHelloDone訊息後傳送的第一條訊息。
透過此訊息,設定預主金鑰,方法是直接傳輸RSA加密的秘密或傳輸Diffie-Hellman引數,使雙方能夠達成相同的預主金鑰。
struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;// RSA加密的預主金鑰
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;// 客戶端的Diffie-Hellman公鑰值
} exchange_keys;
} ClientKeyExchange;
- 計算主金鑰
對於所有金鑰交換方法,使用相同的演算法將pre_master_secret轉換為master_secret。一旦計算出master_secret,pre_master_secret應從記憶體中刪除。
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)//偽隨機函式
[0..47];
- RSA
當使用RSA進行伺服器認證和金鑰交換時,客戶端生成一個48位元組的pre_master_secret,使用伺服器的證書中的公鑰加密,併傳送給伺服器。伺服器使用其私鑰解密得到pre_master_secret。然後雙方按照上述規定將pre_master_secret轉換為master_secret。此結構是ClientKeyExchange訊息的變體,不是獨立訊息。
PreMasterSecret中的版本號是客戶端在ClientHello.client_version中提供的版本,而不是為連線協商的版本。此功能設計用於防止回滾攻擊。
struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret;
struct {
public-key-encrypted PreMasterSecret pre_master_secret;//由客戶端生成的隨機值,用於生成主金鑰
} EncryptedPreMasterSecret;
- Diffle-Herman
如果客戶端使用了Diffie-Hellman金鑰交換方法,客戶端需要向伺服器傳遞其Diffie-Hellman公鑰值(Yc)。根據情況不同,Yc的傳遞可以是顯式的或隱式的。
enum { implicit, explicit } PublicValueEncoding;
//implicit:客戶端已經傳送了包含Diffie-Hellman金鑰的證書,因此不需要再次傳送Yc。客戶端金鑰交換訊息將被髮送,但內容為空。
//explicit:需要傳送Yc。
struct {
select (PublicValueEncoding) {
case implicit: struct { };
case explicit: opaque dh_Yc<1..2^16-1>;
} dh_public;//客戶端的Diffie-Hellman公鑰值(Yc)
} ClientDiffieHellmanPublic;
④CertificateVerify
此訊息用於對客戶端證書進行顯式驗證。此訊息僅在客戶端證書具有簽名能力(即,除包含固定Diffie-Hellman引數的證書外的所有證書)後傳送。傳送時,它必須緊跟在客戶端金鑰交換訊息之後。
struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;
這裡的handshake_messages指的是從客戶端hello開始到(但不包括)此訊息為止傳送或接收的所有握手訊息,包括握手訊息的型別和長度欄位。這是迄今為止交換的所有握手結構(如第7.4節所定義)的連線。注意,這要求雙方要麼快取訊息,要麼為所有潛在的雜湊演算法計算執行雜湊,直到CertificateVerify計算時。伺服器可以透過在CertificateRequest訊息中提供有限的摘要演算法集合來最小化此計算成本。
⑤Finished
完成訊息(Finished)是TLS握手過程中的最後一個握手訊息,用於確認整個握手過程的完整性和正確性。它是第一個使用剛協商的加密演算法、金鑰和秘密進行保護的訊息。
完成訊息總是在更改密碼規範訊息(ChangeCipherSpec)之後立即傳送。這個順序是必須的,因為完成訊息是用新協商的加密演算法和金鑰保護的。
struct { opaque verify_data[verify_data_length]; } Finished; //verify_data是一個透過偽隨機函式(PRF)計算的驗證資料 PRF(master_secret, finished_label, Hash(handshake_messages))[0..verify_data_length-1]; /* finished_label: 客戶端傳送的完成訊息使用字串:"client finished"。 伺服器傳送的完成訊息使用字串:"server finished"。 */ /* handshake_messages: 這是到目前為止交換的所有握手訊息的雜湊值,不包括HelloRequest訊息和記錄層頭。 */
2.DTLS(1.2)
2.1背景
單對比 TLS 1.2,DTLS 1.2 大部分步驟都是一樣的,只是在服務端多了一步 HelloVerifyRequest,客戶端因此也多了第二次的 ClientHello
2.2協議細則
(1)架構
(2)流程
DTLS使用與TLS相同的所有握手訊息和流程,但有三個主要變化:
- 新增了無狀態的cookie交換,以防止拒絕服務攻擊。
- 修改了握手頭部以處理訊息丟失、重新排序和DTLS訊息分片(為了避免IP分片)。
- 為了處理訊息丟失而設定了重傳定時器。
2.3協議實現
(1)記錄層
略
(2)握手層
為了支援訊息丟失、重新排序和訊息分片,DTLS修改了TLS 1.2的握手頭部結構:
enum {
hello_request(0), client_hello(1), server_hello(2),
hello_verify_request(3), // 新欄位
certificate(11), server_key_exchange(12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255) } HandshakeType;
struct {
HandshakeType msg_type;
uint24 length;
uint16 message_seq; // 新欄位
uint24 fragment_offset; // 新欄位
uint24 fragment_length; // 新欄位
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case hello_verify_request: HelloVerifyRequest; // 新欄位
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
-
訊息序列號(
message_seq
):每條握手訊息都有一個序列號,以確保訊息的完整性和順序。序列號從0開始,每傳送一條訊息遞增。這有助於在訊息重傳時維持順序。
在每次握手中,每方傳輸的第一條訊息的message_seq
總是為0。每當生成新訊息時,message_seq
的值增加1。
-
片段偏移(
fragment_offset
)和片段長度(fragment_length
):這兩個新欄位允許將較大的握手訊息分成多個片段進行傳輸。這是因為UDP等資料包協議不保證單一訊息的完整性,所以需要手動處理訊息的分片和重組。 -
新的握手型別(
hello_verify_request
):這是DTLS特有的訊息型別,用於驗證客戶端,作為防止DoS攻擊的一部分。
①ClientHello
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
opaque cookie<0..2^8-1>; // 新欄位
CipherSuite cipher_suites<2..2^16-1>;
CompressionMethod compression_methods<1..2^8-1>;
} ClientHello;
②Certificate
隨機數 |
生成方 |
生成報文 |
A |
Client |
ClientHello |
B |
Server |
ServerHello |
C |
Client |
—— |