Session會話恢復:兩種簡短的握手總結SessionID&SessionTicket

目錄

 

完整的握手

會話恢復

Session ID會話恢復流程

回退攻擊

Session ID分佈式負載均衡問題

SessionTicket

恢復過程

NewSessionTicket子消息


完整的握手

當客戶端和服務器端初次建立TLS握手時(例如瀏覽器訪問HTTPS網站),需要雙方建立一個完整的TLS連接,該過程爲了保證數據的傳輸具有完整性和機密性,需要做很多事情,密鑰協商出會話密鑰,數字簽名身份驗證,消息驗證碼MAC等,整個握手階段比較耗時的地方是密鑰協商,需要密集的CPU處理。當客戶端和服務器完成一次完整的握手過程後,它們之間發送的數據就會一直有TLS保護,當某一時刻客戶端和服務器斷開了本次會話連接,那麼它們之前連接時協商好的會話密鑰(動態密鑰)就沒用了,消失了,因爲要保證前向安全性,客戶端和服務器端都不會去保存加密參數。在下一次客戶端訪問同一個HTTPS網站,也就是再次訪問該服務器時,便要進行一次新的完整的握手階段,這似乎沒什麼問題,但是當一個網站用戶量越來越大後,某一時間段裏大量的請求提交,佔用了服務器資源,會導致很大的網絡延遲。

原因很簡單,上面說到,每一次客戶端與服務器端完整的握手階段要做很多事情,需要一些時間,即使是同一個客戶端,關閉連接後訪問同一個HTTPS網站都要進行一次新的完整握手。握手協議完成後,服務器會在自己的內存中保存本次會話的一些信息:會話標識符,證書、協商出的密碼套件、使用的壓縮算法(還是提一句,一般不使用- -),主密鑰和會話可恢復標識,注意此時會話保持正常連接狀態,直到會話被關閉後,這些信息纔會消失。

 

會話恢復

爲了解決上面的問題,TLS/SSL協議中提供了會話恢復的方式,允許客戶端和服務器端在某次關閉連接後,下一次客戶端訪問時恢復上一次的會話連接。會話恢復有兩種,一種是基於Session ID的恢復,一種是使用SessionTicket TLS擴展。

Session ID會話恢復流程

每一個會話都由一個Session ID標識符標識,這在Client Hello子消息和Server Hello子消息中我們都看到過,作用是根據這個Session ID來進行會話恢復。建立一個TLS連接後客戶端和服務器端都會保存Session ID,主要由服務器保存,最後是否支持恢復會話是服務器決定,先來看看Session ID在TLS握手階段的傳遞過程:

  1. 首先客戶端握手階段發送Client Hello子消息,裏面的Session ID值是爲空的。
  2. 服務器端接收到Client Hello,查看裏面的Session ID值,如果值爲空,表明雙方是第一次握手,需要進行一次完整的握手階段,然後生成一個新的Session ID,表示本次會話,並放到Server Hello子消息中迴應給客戶端。
  3. 客戶端收到Server Hello,將Session ID存放在本地內存種,接着發送密碼切換協議和Finished子消息進行校驗,確保握手消息沒有被篡改,告訴TLS記錄層需要的密鑰塊已經準備好了。
  4. 客戶端和服務器端各自將Session ID保存在自己本地中,客戶端保存在內存裏,服務器端保存在Server Cache中。

一次完整的握手階段結束後,客戶端和服務器端都保存有這個Session ID,在本次會話關閉,下一次再次訪問相同的HTTPS網站時,客戶端瀏覽器會在Client Hello子消息中附帶這個Session ID值,服務器端接收到請求後,將Session ID與自己在Server Cache中保存的Session ID進行匹配,如果匹配成功,那麼服務器端就會恢復上一次的TLS連接,使用之前協商過的密鑰,不重新進行密鑰協商,服務器收到帶Session ID的Client Hello且匹配成功後,直接發送ChangeCipherSpec子協議,告訴TLS記錄層將連接狀態切換成可讀和可寫,最後發送Finished,會話恢復成功,本次握手爲一次簡短的握手,而不是完整的一次握手。

回退攻擊

要注意的是,即使連接採用了會話恢復的方式,使用之前協商過的密碼套件和主密鑰,客戶端和服務器端在進行簡短的握手時還是要校驗Finished子消息保證前面的握手消息沒有被篡改的。Finished子消息的發送順序一定是在發送ChangeCipherSpec之後,ChangeCipherSpec密碼切換協議的作用是在密鑰協商完成後,告訴TLS記錄層需要的密鑰塊已經準備好,接下來TLS記錄層協議可以對應用層數據進行加密了,到這一步之後才發送Finished給對方進行校驗。對於客戶端來說,Finished子消息包含了前面自己發送的和收到的所有握手消息,而對於服務器端來說也是,包含了客戶端的Finished子消息。

如果發送的順序出錯,Finished子消息發在了ChangeCipherSpec之前,或者沒有發送Finished,會出現“回退攻擊”的問題,例如連接過程中,客戶端在Client Hello裏使用的ProtocolVersion是TLS v1.2,告訴服務器我支持的TLS協議版本最高爲1.2版本,此消息在發送過程在被中間方劫持並篡改,將ProtocolVersion修改爲較低的版本如TLS v1.0。服務器端在收到ChangeCipherSpec後,由於發送順序問題(例如Finished發送在了ChangeCipherSpec前面),導致沒有去校驗Finished握手消息,使用了前面Client Hello中的v1.0版本協議進行加密通信,那麼中間方就可以利用低版本協議的問題進行攻擊。如果Finished嚴格發送在了ChangeCipherSpec的後面,在服務器最後接收到客戶端的Finished子消息後,會校驗出前面的Client Hello,發現支持的最高協議版本號是TLS v1.2,與自己前面收到的Client Hello中不同,即消息被篡改了,那麼服務器可以終止握手。

 

Session ID分佈式負載均衡問題

雖然使用Session ID進行會話恢復可以減少一些耗時的步驟,但由於Session ID主要保存在服務器Server Cache中,且沒有考慮到多個服務器主機共享Server Cache,會出現一些問題,試想下面的場景:

一個HTTPS網站有多個服務器提供服務,客戶端第一次與服務器建立完整的TLS握手,Session ID保存在了提供本次服務的服務器Server Cache中,會話關閉後,客戶端訪問同一個HTTPS網站,這次連接請求由於分佈式負載均衡設定將請求重定位到了其他服務器上(提供同樣的網站服務),此時新的服務器Server Cache中沒有緩存與客戶端匹配的Session ID,導致會話恢復無法進行。

 

SessionTicket

第二種會話恢復方式是使用SessionTicket TLS擴展,大致過程是這樣的,客戶端和服務器端建立了一次完整的握手過程後,服務器端將本次的會話數據進行加密,例如前面說到的,會話標識符、證書、密碼套件和主密鑰等,加密後生成一個ticket票據,並將票據通過NewSessionTicket子消息發送給客戶端,由客戶端來保存,下一次連接時客戶端如果希望恢復上一次會話而不是重新進行握手,就將“票據”一起發送給服務器端,待服務器端解密校驗無誤後,進行一次簡短的握手,恢復上一次會話。

恢復過程

如上圖所示,如果客戶端和服務器端想使用SessionTicket進行會話恢復,那麼在第一次建立TLS握手時,就要發送SessionTicket TLS擴展:

  1. 客戶端與服務器端第一次連接,在Client Hello子消息中使用空的SessionTicket TLS擴展。
  2. 服務器解析出Client Hello中的SessionTicket擴展後,如果選擇支持,那麼在自己的Server Hello中也附帶一個空的SessionTicket擴展,告訴客戶端我支持會話恢復。
  3. 接下來可以繼續進行握手過程,待客戶端發送ChangeCipherSpec和Finished子消息後,服務器端將這些加密參數加密生成票據,通過NewSessionTicket子消息發送給客戶端。
  4. 客戶端保存票據,下一次連接如果想通過會話恢復的方式,則可以將票據附帶到Client Hello的SessionTicket TLS擴展中去,此時的擴展就不是empty了。

第一次完整的TLS握手完成後,待下一次客戶端訪問相同的HTTPS網站,如果想要進行會話恢復,則在Client Hello子消息中附帶票據到SessionTicket TLS擴展至,服務器收到非空的擴展後,會去校驗裏面的票據信息,會話恢復完成簡短的握手後,服務器端會發送新的NewSessionTicket子消息給客戶端,裏面是新的票據,爲什麼會話恢復後要發一個新的票據給客戶端?因爲在服務器簽發的票據中有一個需要更新的屬性,票據的有效期,如果校驗到票據是過期的,那麼本次會話恢復請求失敗,Ticket票據的屬性存在於NewSessionTicket子消息中。

NewSessionTicket子消息

上面說到,使用SessionTicket方式進行會話恢復,在第一次客戶端與服務器端建立完整的TLS握手時,客戶端便會發送SessionTicket TLS擴展,在最後客戶端發送了ChangeCipherSpec和Finished子消息後,由服務器端生成票據,發送NewSessionTicket子消息給客戶端保存,也就是說票據信息存在於該子消息中:

struct {
	uint32 ticket_lifetime_hint; //有效期
	opaque ticket<0..2^16-1>; //票據數據結構
} NewSessionTicket;

struct {
	opaque key_name[16]; //票據加密使用的密鑰
	opaque iv[16]; //初始化向量
	opaque encrypted_state<0..2^16-1>; //票據詳細屬性
	opaque mac[32];
} ticket;

可以看到票據的有效期屬性和一個ticket結構,標識票據信息,如使用的加密密鑰,初始化向量等,encrypted_state屬性十分重要,它存儲的是本次會話的信息,屬性有密碼套件,壓縮算法,主密鑰和票據過期時間等:(ticket)

struct {
	ProtocolVersion protocol_version;
	CipherSuite cipher_suite;
	compressionMethod compression_method;
	opaque master_secret[48];
	ClientIdentity client_identity;
	uint32 timestamp;
} StatePlaintext;

就是最前面說到的,客戶端和服務器端第一次建立完整TLS握手後會保存本次會話的信息,待下次會話恢復用。

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