TLS:接近TCP協議底層的記錄層

作爲構建在TCP之上應用層之下的協議,TLS是獨立開來的協議,任何應用層協議都可以直接引入使用它,其最重要的作用就是將應用層協議,如FTP,HTTP或SMTP等加密處理後,再傳遞給下層的TCP協議來保證數據傳輸的可靠性。TLS將來自高層的協議數據進行封裝,過程中分有兩層結構,握手層和記錄層,握手層在上,記錄層在下,所以握手層更接近應用層,記錄層更接近傳輸層TCP。記錄層協議的作用大致有,將上層協議封裝,將數據分塊,數據壓縮(通常不使用),最重要的數據加密解密,驗證,這篇日誌就來總結下TLS協議的記錄層。

 

記錄層消息頭

記錄層協議將上層的握手層一條或多條子消息,添加上自己的消息頭,最終封裝成一條消息,再往下傳遞給TCP處理,那麼記錄層的消息頭裏都有些什麼呢?來看看:

記錄層協議的消息頭一共5個字節組成,分爲三部分,首先是消息類型,佔一個字節,消息類型有四種,對應的是上一層握手層的四種自協議:Change Cipher Spec Protocol密碼切換協議、Alert Protocol警告協議,TLS Handshaking Protocols握手協議和Application Data Protocol應用層協議,對應的十六進制編號按順序分別爲0x14到0x17。接着是兩個字節的版本號,版本號標誌的是TLS協議的版本號,TLS1.0—TLS1.3,最新的版本是2018年更新的TLS1.3。再往下是兩個字節的消息長度,故名思意是標識本條消息的長度,但是注意,不是指記錄層消息頭的長度,消息頭長度是固定5個字節的(當然未來可能會擴展消息類型的字節長度),記錄層消息頭裏的消息長度標識的是記錄層將上層協議封裝成一條消息後的消息長度。

 

連接狀態

HTTPS裏客戶端和服務器端每次建立連接,也就是建立一個session會話時,都會有一個連接狀態,一個TLS連接狀態標識的就是一個記錄層的環境,連接狀態的不同標識着LTS協議的不同過程,一共有四種:

pending read states(待讀狀態)

pending write states(待寫狀態)

current read states(可讀狀態)

current write states(可寫狀態)

加密參數

在客戶端和服務器端一開始建立連接時,雙方都處於待讀狀態和待寫狀態,等待加密參數準備好(加密參數填充到密碼套件中),加密參數中指定了加密函數(如aes算法),加密模式(流密碼,塊密碼或者aead),還有密鑰長度,MAC消息驗證碼算法,主密鑰等。這些加密參數都由記錄層的上一層握手層協議提供,TLS記錄層得到加密參數後,客戶端和服務器端就可以改變連接狀態,使用加密參數的值進行加密和解密操作。客戶端和服務器端互相發送Change Cipher Spec Protocol密碼切換協議消息給對方,提示可以進行連接狀態的切換,隨後雙方都切換爲可讀狀態和可寫狀態,使用加密參數進行加密解密。

記錄層處理過程

連接狀態完成後,TLS記錄層協議的主要工作還沒完,前面說到,TLS記錄層會將上層的消息添加消息頭,封裝處理後再交給下層TCP傳輸層,除了添加消息頭外,記錄層還做了數據分塊,數據壓縮和數據加密的工作,總的來說,TLS記錄層協議的主要工作就是這四部分。

數據分塊

當上層的握手層協議消息進入TLS記錄層後,首先進行數據分塊,分塊成大小不超過2^14字節的TLSPlaintext數據結構:

//TLS協議協商得到的版本號
struct {
	uint8 major;
	uint8 minor;
} ProtocolVersion;

//TLS上層協議的四個子協議
enum {
	  change_cipher_spec(20), 
	  alert(21), 
	  handshake(22),
	  application_data(23), 
} ContentType;

struct {
	  ContentType type;
	  ProtocolVersion version;
	  uint16 length;
	  opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

從TLSPlaintext結構中可以看到,ContentType標示的是上層的協議的類型,ProtocolVersion標識的是TLS協議的版本號,length標識的是數據分塊後的塊大小,長度不超過2^14字節,最後一個fragment是上層協議消息,此時還沒有進行機密性保護,屬於明文處理。

數據壓縮

數據壓縮的數據結構TLSCompressed如下:

struct {
	  ContentType type;
	  ProtocolVersion version;
	  uint16 length;
	  opaque fragment[TLSCompressed.length];
} TLSCompressed;

數據壓縮算法將TLSPlaintext數據結構轉換成TLSCompressed結構,如果不選擇進行數據壓縮,那麼TLSPlaintext結構就不會被轉換成TLSCompressed結構。在TLS1.2版本協議中,壓縮算法一般不會被使用,因爲安全問題,在TLS1.3版本里直接將數據壓縮操作給刪除了,增加了一個數據填充操作,數據發送方可以選擇在數據加密前爲消息填充一段零值字節,之後再進行加密,目的是爲了隱藏真實消息長度,如果選擇填充,那麼TLSPlaintext結構會被轉換成TLSInnerPlaintext結構。

加密

數據加密部分,消息進行壓縮(通常不使用),填充之後,接下來進行加密,上述的TLSPlaintext,TLSCompressed或者TLSInnerPlaintex結構會被轉換爲TLSCiphertext結構:

struct {
	ContentType type;
	ProtocolVersion version;
	uint16 length;
	select (SecurityParamters.cipher_type) {
		case stream: GenericStreamCipher;
		case block: GenericBlockCipher;
		case aead: GenericAEADCipher;
	} fragment;
} TLSCiphertext;

      前面三個屬性不用多加解釋了,fragment是最終加密後的數據,加密方式有三種,流密碼,塊密碼和AEAD三種。加密後的消息末尾還要加上MAC值,裏面包含了一個序列號,序列號十分重要,用來檢測消息是否有丟失或者重複等,具體的做法是:

  1. 初始化時客戶端和服務器端都各自維護一個序列號,序列號初值爲0。
  2. 客戶端與服務器端建立連接,客戶端生成client_recv和client_send兩個變量,分別用來記錄客戶端處接收的數據塊總量和發送數據塊總量,服務器端同理。
  3. 客戶端發送一個消息後,client_send加一,服務器端接收到一個客戶端消息後,對MAC值進行校驗,記錄serve_recv。如果客戶端和服務器端之間消息發送沒有出現丟失或者重複的錯誤,那麼client_send的值就會等於server_recv的值,server_send的值等於client_recv的值。

添加消息頭

最後,經過加密處理的LSCiphertext結構添加記錄層消息頭,就可以傳遞給下層TCP傳輸層了,消息頭的內容,上面已經總結,包含了一個字節的消息類型,兩個字節的版本號和消息長度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章