代 霞,黃勁松
(中國電子科技集團公司第三十研究所,四川 成都 610041)
安全套接層(Secure Socket Layer,SSL)和傳輸層安全(Transport Layer Security,TLS)是世界上使用最廣泛的密碼通信框架。其設計目標是在基于連接的傳輸層上提供密碼學安全性、通用性、可擴展性和高效性保障。從1995 年誕生到現(xiàn)在,SSL/TLS 發(fā)布了SSL2.0、SSL3.0、TLS1.1、TLS1.2 和TLS1.3 共5 個版本。TLS1.2 是現(xiàn)在使用最廣泛的版本,但TLS1.3 從2018 年發(fā)布以來,也開始被國內(nèi)外一些網(wǎng)站使用,Chrome、Firefox、OpenSSL、Nginx 等均提供了相應支持,它已經(jīng)悄然進入了人們的生活。
TLS 協(xié)議介于應用層和傳輸層之間,對上承載超文本傳輸協(xié)議(Hyper Text Transfer Protocol,HTTP)、簡單郵件傳輸協(xié)議(Simple Mail Transfer Protocol,SMTP)、郵局協(xié)議版本3(Post Office Protocol -Version 3,POP3)等應用協(xié)議。TLS1.2包括握手協(xié)議(handshake)、更改加密規(guī)范協(xié)議(change cipher spec)、警告協(xié)議(alert)和應用數(shù)據(jù)協(xié)議(application data)[1]4 個子協(xié)議。和TLS1.2 相比,TLS1.3 去掉了更改加密規(guī)范協(xié)議[2-3]。TLS 的完整握手過程要進行RSA、橢圓曲線迪菲-赫爾曼秘鑰交換(Elliptic Curve Diffie-Hellman key Exchange,ECDH)、橢圓曲線數(shù)字簽名算法(Elliptic Curve Digital Signature Algorithm,ECDSA)等非對稱計算,但非對稱計算速率較慢。為了提高性能,TLS1.2和TLS1.3 協(xié)議中提供了會話復用的方式,允許客戶端和服務器在某次關閉連接后,下一次客戶端訪問時恢復上一次的會話連接[4-5]。為了實現(xiàn)會話復用,客戶端需要保存首次連接的會話信息。本文在對TLS1.2、TLS1.3 會話復用方式梳理的基礎上,分析存儲在客戶端會話信息可能面臨的安全風險和攻擊,提出對客戶端信息進行密碼學保護的改進措施,期望增強握手過程中數(shù)據(jù)傳遞的安全性[6]。
握手協(xié)議是TLS 中最精密復雜的協(xié)議,每一個TLS 連接都會以握手開始。如果客戶端此前并未與服務器建立會話,那么雙方會執(zhí)行一次完整的握手流程來協(xié)商TLS 會話。
TLS 完整的握手過程中要進行非對稱計算,非對稱計算速度慢且資源消耗多。為了提高握手效率,TLS 從設計之初就采用了復用方法,把握手的結(jié)果直接保存起來,繞過握手中密鑰交換和身份認證過程。TLS1.2 中有會話標識(session id)和會話票據(jù)(session ticket)兩種復用方式。TLS 1.3 直接內(nèi)置session ticket,并改名為預共享密鑰(Pre-Shared Key,PSK)。
當?shù)谝淮挝帐纸Y(jié)束后,若需要緩存該會話,客戶端將服務器的IP 地址端口與session id 和master secret 等關聯(lián)起來,保存在本地。
當?shù)诙挝帐謺r,客戶端要使用上次會話結(jié)果,則根據(jù)服務器的IP 地址端口找到session id,并在client hello 中的session id 位置上添加該值。服務器收到這個client hello,解析session id,查找本地是否存在該session。
如果存在,判斷當前的加密套件和上個會話的加密套件是否一致。如果一致,把找到的session狀態(tài)恢復到當前連接,在server hello 中的session id 處也添加和client hello 中一樣的值,然后得到session id 對應的master secret 并計算會話密鑰。最后,客戶端和服務器都必須發(fā)送ChangeCipherSpec和Finished 消息。這幾步完成后,雙方開始交換應用數(shù)據(jù)。
如果服務器沒有找到session id 或者不想復用該會話(比如會話已經(jīng)老化),那么服務器就生成一個新的session id 在server hello 里發(fā)給客戶端,并且雙方進行完整的握手。
在session id 會話復用中,服務器端也需要保存session,如果TLS 服務器不止一臺的話,就有一個存儲怎么共享的問題,要么存儲同步到所有TLS 服務器的內(nèi)存里,要么專門搞服務來支持存儲,并使用RPC 訪問。無論如何,都是很麻煩的事情。相比之下,session ticket 比session id 簡單多了,一般優(yōu)先使用session ticket。session ticket 是一種不需要服務器端保存狀態(tài)的復用方式。TLS1.2 中NewSessionTicket、ticket 和StatePlaintext 結(jié)構(gòu)如下:
ticket_lifetime_hint 為ticket 的生命周期(老化時間),以秒為單位。encrypted_state 存儲的是本次會話信息,包括版本、密碼套件、壓縮算法、主密鑰和票據(jù)過期時間等。
session ticket 的工作流程如下文所述。
(1)客戶端發(fā)起client hello,拓展中帶上空的session ticket,表明自己支持session ticket。
(2)服務器在握手過程中,如果支持session ticket,則把session 狀態(tài)存入一個StatePlaintext 中,隨機生成IV,加密StatePlaintext 生成encrypted_data,對key_name、IV、encrypted_data 的長度和encrypted_data 計算消息認證碼(Message Authentication Code,MAC)。最后把各個字段填入上面的ticket 結(jié)構(gòu)體。加密和計算MAC 用的key 只有服務器知道。加密并計算MAC 過的ticket 用NewSessionTicket 消息發(fā)給客戶端,該消息應該在ChangeCipherSpec 消息之前,并在服務器驗證通過客戶端的Finished 消息之后發(fā)送。
(3)客戶端收到這個NewSessionTicket,就把服務器地址端口、NewSessionTicket 和當前的master secret 等其他與當前session 有關的參數(shù)保存起來。對于客戶端來說,ticket 就是一塊二進制buffer,并不關心里面的內(nèi)容。當客戶端嘗試會話復用時,就把ticket 包含在client hello 的session ticket 擴展中發(fā)給服務器。
(4)服務器收到后解密ticket,計算MAC 確認ticket 沒有被篡改過,然后從解密的內(nèi)容里獲取session 狀態(tài)恢復會話,也可以在server hello 之后返回一個NewSessionTicket 消息來更新ticket。如果服務器不能或者不想使用客戶端發(fā)來的ticket,那么服務器可以忽略ticket,啟動一個完整的握手流程。
TLS1.3 淘汰了session id 和session ticket 這兩種機制,由PSK 實現(xiàn)會話復用[7]。PSK 是TLS1.2 中復用(Resumption)機制的一個升級,它主要使用的是session ticket 進行會話復用,但是又不同于TLS1.2 中使用session ticket 進行會話復用的過程,它做出了一些改變,或者說是進行了一些更新。
TLS1.3 完整握手結(jié)束后,服務器可以發(fā)送一個NST(NewSessionTicket)的報文給客戶端,該報文中記錄PSK 的值、名字和有效期等信息,雙方下一次建立連接可以使用該PSK 值作為初始密鑰材料。TLS1.3 中NewSessionTicket 結(jié)構(gòu)如下:
ticket_lifetime 和TLS 1.2 中的ticket_lifetime_hint含義一樣,表示ticket 的老化時間(生存時間)。這個時間是以ticket 發(fā)布時間開始計算,為網(wǎng)絡字節(jié)序的32 位無符號整數(shù),以秒為單位。ticket_age_add 為隨機生成的32 位整數(shù),主要是用來混淆PSK 中攜帶的session ticket 老化時間。服務器必須為它發(fā)出的每個ticket 生成一個新值。ticket_nonce就是一個計數(shù)器,初始值是0,發(fā)送一次后,執(zhí)行counter++。
ticket 和TLS1.2 中的含義一致,encrypted_state的明文和TLS1.2 中的StatePlaintext 類似,只是這里是PSK,而不是master secret。extensions 為一組擴展的ticket 值,目前只定義了一種拓展——max_early_data_size。該值的作用表明服務器愿意接收多少字節(jié)的early data,超過這個值,服務器會斷開連接。
TLS1.3 使用PSK 會話復用時,客戶端發(fā)送client hello 時包括pre_shared_key 擴展,pre_shared_key 擴展其實就是Tickets+binders。由于TLS 1.3 中,服務器的NewSessionTicket 可以在握手結(jié)束后隨時隨地地發(fā)送,且可能發(fā)送多次,這意味著客戶端會緩存多個NewSessionTicket,所以pre_shared_key 會保存多對Tickets+binders 組合。
服務器發(fā)送server hello 時包括pre_shared_key 擴展,表示自己正常解析了客戶端發(fā)送的pre_shared_key,然后指定從Tickets+binders 中選擇的序號,用數(shù)字0,1,2,3,…表示。
TLS1.3 中PreSharedKeyExtension 結(jié)構(gòu)如下:
Identity的內(nèi)容就是NewSessionTicket中的ticket 部分,服務器用其來恢復session。obfuscated_ticket_age 為密鑰時期的混淆版本,以毫秒為單位。client hello 是明文傳輸?shù)?,該字段必然也是明文傳輸?shù)模灾虚g人能夠看到。為了不讓中間人知道這個ticket 的年齡,很容易想到的是對這個值加鹽。這個鹽就是服務器端發(fā)送的NewSessionTicket 中的ticket_age_add。因為NewSessionTicket 本身就是被加密的,所以這個ticket_age_add 只有通信雙方才知道。identities 為客戶端愿意與服務器協(xié)商的身份列表。如果和early_data 一起發(fā)送,第一個身份被用來標識 0-RTT。binders 在PSK 和當前握手之間以及在建立PSK 的會話之間形成綁定。綁定器列表中的每個條目被計算為直到并包括PreSharedKeyExtension.identities 字段的client hello 的部分(包括握手報頭)上的HMAC。它包括所有client hello,但不包括綁定者列表本身。selected_identity 為服務器選擇的身份,用數(shù)字0,1,2,3,…表示選擇了客戶端身份列表中第幾個。
在TLS1.2 會話復用中,客戶端和服務器雙方會產(chǎn)生新的ClientHello.random 和ServerHello.random,最后和session 的master secret 一同通過偽隨機函數(shù)(Pseudo Random Function,PRF)計算生成新的會話密鑰,新的連接完全獨立于前一個連接。
使用session id 方式,完整握手結(jié)束后,客戶端將session id 和master secret 等會話參數(shù)保存在本地。服務器也將session id 和master secret 關聯(lián)起來,建立一個session 掛鏈等待后續(xù)使用。后續(xù)握手發(fā)起時,客戶端將session id 包含在client hello 中發(fā)送給服務器。由于client hello 和server hello 以明文形式在網(wǎng)絡中傳輸,攻擊者能夠通過會話劫持方式獲取ClientHello.random、ServerHello.random 和session id,因此客戶端會話信息的安全性顯得尤為重要[8]。如果攻擊者得到了這些信息,首先通過session id 找到master secret,其次以相同的計算方法獲取新連接的會話密鑰,最后解密客戶端和服務器傳輸?shù)膽脭?shù)據(jù),這樣便成為中間人監(jiān)聽雙方通信[9-10]。攻擊者也可以偽裝成真實客戶端,在client hello 中將session id 發(fā)送給服務器,和服務器建立連接。此時攻擊者一方面可以訪問服務器資源造成信息泄漏;另一方面也可以發(fā)送大量請求,讓服務器陷于繁忙的處理中造成拒絕服務(Denial of Service,DoS)攻擊。
使用session ticket 方式,完整握手結(jié)束后,客戶端保存的信息除了本地session 參數(shù),還需增加服務器的NewSessionTicket。服務端僅需保存加密ticket 的key。后續(xù)握手發(fā)起時,客戶端將ticket 包含在client hello 中發(fā)送給服務器。該ticket 以密文形式在網(wǎng)絡中傳輸,加密的key 只有服務器知道。攻擊者可能會竊取到ticket,并且嘗試用來和服務器建立會話。但是由于不知道key,竊取到的ticket不足以恢復會話。如果攻擊者得到了客戶端會話信息,可以通過ticket 找到master secret,然后以類似的方式造成中間人攻擊。攻擊者也可以在client hello中將ticket 發(fā)送給服務器,仿冒真實用戶與服務器建立連接和通信,對服務器安全性造成威脅[11-12]。
在TLS1.3 會話復用中,客戶端和服務器使用協(xié)商出來的key 和PSK 組成初始密鑰材料,和握手階段報文一起作為基于密鑰相關的哈希運算消息認證的密鑰推導(HMAC-based Key Derivation Function,HKDF)函數(shù)的輸入,計算出新的會話密鑰。PSK 和session ticket 復用本質(zhì)上大體一致。完成完整握手后,客戶端將NewSessionTicket 和PSK等會話參數(shù)保存起來。服務端行為與使用session ticket 方式保持一致。后續(xù)握手發(fā)起時,客戶端將愿意與服務器協(xié)商的身份列表identities 和綁定器值列表binders 包含在client hello 中發(fā)送給服務器。每個身份列表包括ticket 和該ticket 混淆版本壽命,與PSK 關聯(lián)。服務器如果同意復用會話,會將選擇的身份索引selected_identity 通過server hello 發(fā)給客戶端[13-14]。攻擊者如果竊取到identities 列表信息,同樣由于ticket 被加密,且不知道加密的key,無法從ticket 中獲取PSK 恢復會話。如果攻擊者得到了客戶端會話信息,可以通過selected_identity、ticket找到PSK,然后以類似的方式造成中間人攻擊。攻擊者也可以在client hello 中將identities 和binders發(fā)送給服務器,以類似方式竊取和占用服務器資源甚至造成DoS 攻擊[15-16]。
為了實現(xiàn)會話復用,采用session id、session ticket 和PSK 方式都要在客戶端保存相關的會話信息。如果這些信息以明文的形式存儲,一旦被攻擊者竊取,將會對后面復用的會話帶來嚴重的安全隱患。由此可見,應該對客戶端會話信息提供機密性和完整性保護。機密性指信息不能被竊聽,通常使用加密功能實現(xiàn)。完整性指信息不能被篡改,通常使用認證功能實現(xiàn)。
在加密和認證的順序上,TLS1.2 采用MACthen-Encrypt 方式,即先計算MAC,然后把“明文+MAC”做流加密或者塊加密。但是,近些年人們發(fā)現(xiàn)針對MAC-then-Encrypt 這種結(jié)構(gòu)很容易構(gòu)造padding oracle 相關的攻擊,導致BEAST 攻擊、Lucky 13 攻擊和POODLE 攻擊[6]。學術(shù)界一致同意:Encrypt-then-MAC 才是最安全的。于是把Encrypt 和MAC 直接集成為一個算法,在算法內(nèi)部解決好安全問題,這就是用于關聯(lián)數(shù)據(jù)的認證加密(Authenticated-Encryption with Additional Data,AEAD)類的算法,伽羅瓦/計數(shù)器模式(Galois/Counter Mode,GCM)就是AEAD 中最重要的一種[5]???以 在aes-gcm-128、aes-gcm-256 和chacha20-poly13053 種主流的AEAD 算法中選擇其一作為客戶端會話信息加密和認證的算法。
在密鑰key 的選擇上應該考慮以下幾個方面:
(1)key 除了用于會話信息的加密認證外,不應該有其他用途;
(2)整個會話信息不能只使用一個key,這樣即使key 發(fā)生泄漏,也不會危及所有會話信息的安全;
(3)key 應該定期更換。
鑒于上述考慮,將客戶端會話信息按照某種方式比如服務器IP 地址進行分類,相同的服務器會話使用相同的key 加密,key 的更換周期與session或者session ticket 的生命周期關聯(lián)。由于session 的生命周期不超過1 天,session ticket 的生命周期不超過7 天,加密session 和session ticket 的key 更換周期分別設置為1 天和7 天。
HKDF 與PRF 相比,前者可以輸出安全性更強的密鑰。HKDF包括extract_then_expand兩階段過程,extract 過程增加密鑰材料的隨機性。PRF 實際上只實現(xiàn)了HKDF 的expand 部分。因此,客戶端采用HKDF 導出key 和初始向量iv,具體步驟如下:
(1)特定的字符串作為salt 值,該字符串在客戶端唯一;
(2)當前日期作為輸入密鑰材料IKM;
(3)通過HMAC-Hash(salt,IKM),計算偽隨機密鑰PRK;
(4)key 和iv 的長度和作為Expand 輸出字節(jié)數(shù)L;
(5)服務器IP 地址作為Expand 可選字符串info;
(6)通過HKDF-Expand(PRK,info,L),計算輸出密鑰材料OKM;
(7)截取OKM 前部分作為key,后部分作為iv。
使用session id 方式,完整握手結(jié)束后,日期信息格式為年-月-日,計算key 和iv,加密session 信息。后續(xù)握手發(fā)起時,先用當前日期信息計算key 和iv,嘗試解密session 信息,解密成功再判斷是否到達生命周期后復用該session,解密失敗則用剛過去1 天的日期信息計算key 和iv,再次嘗試解密session 信息,解密成功再判斷是否到達生命周期后復用該session,解密失敗則刪除該session信息(該信息已經(jīng)超過生命周期或者被篡改過)。
使用session ticket 和PSK 方式,完整握手結(jié)束后,日期信息格式為年-月-周序號,計算key和iv,加密ticket 信息。后續(xù)握手發(fā)起時,首先用當前日期信息計算key 和iv,嘗試解密ticket 信息,解密成功再判斷是否到達生命周期后復用該ticket,解密失敗則用剛過去7 天的日期信息計算key 和iv,再次嘗試解密ticket 信息,解密成功再判斷是否到達生命周期后復用該ticket,解密失敗則刪除該ticket 信息。
本文描述了TLS1.2 和TLS1.3 協(xié)議中的會話復用機制;梳理了采用session id、session ticket 和PSK 方式,客戶端在完整握手和后續(xù)握手階段加密解密master secret、PSK、NewSessionTicket 等信息的具體流程;分析了會話復用機制在客戶端存在的安全漏洞和攻擊;提出了使用AEAD 算法對客戶端信息提供機密性和完整性保護的改進措施;明確了加密key 使用范圍、定期更換和HKDF 導出等細節(jié)。下一步工作的重點是如何在保證客戶端信息安全的前提下實現(xiàn)訪問的高效性。