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