本篇文章我們來(lái)對(duì)比對(duì)比 TLS 1.2 和 TLS 1.3 中的密鑰計(jì)算。 一. TLS 1.2 中的密鑰在 TLS 1.2 中,有 3 種密鑰:預(yù)備主密鑰、主密鑰和會(huì)話密鑰(密鑰塊),這幾個(gè)密鑰都是有聯(lián)系的。 struct { 對(duì)于 RSA 握手協(xié)商算法來(lái)說(shuō),Client 會(huì)生成的一個(gè) 48 字節(jié)的預(yù)備主密鑰,其中前 2 個(gè)字節(jié)是 ProtocolVersion,后 46 字節(jié)是隨機(jī)數(shù),用 Server 的私鑰加密之后通過(guò) Client Key Exchange 子消息發(fā)給 Server,Server 用私鑰來(lái)解密。對(duì)于 (EC)DHE 來(lái)說(shuō),預(yù)備主密鑰是雙方通過(guò)橢圓曲線算法生成的,雙方各自生成臨時(shí)公私鑰對(duì),保留私鑰,將公鑰發(fā)給對(duì)方,然后就可以用自己的私鑰以及對(duì)方的公鑰通過(guò)橢圓曲線算法來(lái)生成預(yù)備主密鑰,預(yù)備主密鑰長(zhǎng)度取決于 DH/ECDH 算法公鑰。預(yù)備主密鑰長(zhǎng)度是 48 字節(jié)或者 X 字節(jié)。 主密鑰是由預(yù)備主密鑰、ClientHello random 和 ServerHello random 通過(guò) PRF 函數(shù)生成的。主密鑰長(zhǎng)度是 48 字節(jié)。可以看出,只要我們知道預(yù)備主密鑰或者主密鑰便可以解密抓包數(shù)據(jù),所以 TLS 1.2 中抓包解密調(diào)試只需要一個(gè)主密鑰即可,SSLKEYLOG 就是將主密鑰導(dǎo)出來(lái),在 Wireshark 里面導(dǎo)入就可以解密相應(yīng)的抓包數(shù)據(jù)。 會(huì)話密鑰(密鑰塊)是由主密鑰、SecurityParameters.server_random 和 SecurityParameters.client_random 數(shù)通過(guò) PRF 函數(shù)來(lái)生成,會(huì)話密鑰里面包含對(duì)稱加密密鑰、消息認(rèn)證和 CBC 模式的初始化向量,對(duì)于非 CBC 模式的加密算法來(lái)說(shuō),就沒(méi)有用到這個(gè)初始化向量。 Session ID 緩存和 Session Ticket 里面保存的也是主密鑰,而不是會(huì)話密鑰,這樣每次會(huì)話復(fù)用的時(shí)候再用雙方的隨機(jī)數(shù)和主密鑰導(dǎo)出會(huì)話密鑰,從而實(shí)現(xiàn)每次加密通信的會(huì)話密鑰不一樣,即使一個(gè)會(huì)話的主密鑰泄露了或者被破解了也不會(huì)影響到另一個(gè)會(huì)話。 二. TLS 1.2 中的 HMAC 和偽隨機(jī)函數(shù)TLS 記錄層使用一個(gè)有密鑰的信息驗(yàn)證碼(MAC)來(lái)保護(hù)信息的完整性。密碼算法族使用了一個(gè)被稱為HMAC(在[HMAC]中描述)的 MAC 算法,它基于一個(gè) hash 函數(shù)。如果必要的話其它密碼算法族可以定義它們自己的 MAC 算法。 此外,為了進(jìn)行密鑰生成或驗(yàn)證,需要一個(gè) MAC 算法對(duì)數(shù)據(jù)塊進(jìn)行擴(kuò)展以增加機(jī)密性。這個(gè)偽隨機(jī)函數(shù)(PRF)將機(jī)密信息(secret),種子和身份標(biāo)簽作為輸入,并產(chǎn)生任意長(zhǎng)度的輸出。 在 TLS 1.2 中,基于 HMAC 定義了一個(gè) PRF 函數(shù)。這個(gè)使用 SHA-256 hash 函數(shù)的 PRF 函數(shù)被用于所有的密碼算法套件。新的密碼算法套件必須顯式指定一個(gè) PRF,通常應(yīng)該使用 SHA-256 或更強(qiáng)的標(biāo)準(zhǔn) hash 算法與 TLS PRF 一同使用。 首先,我們定義一個(gè)數(shù)據(jù)擴(kuò)展函數(shù),P_hash(secret, data),它使用一個(gè) hash 函數(shù)擴(kuò)展成一個(gè) secret 和種子,形成任意大小的輸出: P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + 這里"+"是指級(jí)聯(lián)。 A()被定義為: A(0) = seed 必要時(shí) P_hash 可以被多次迭代,以產(chǎn)生所需數(shù)量的數(shù)據(jù)。例如,如果 P_SHA256 被用于產(chǎn)生 80 字節(jié)的數(shù)據(jù),它應(yīng)該被迭代 3 次(通過(guò) A(3)),SHA_256 每次輸出 32 字節(jié)(256 bit),迭代 3 次才能產(chǎn)生 96 字節(jié)的輸出數(shù)據(jù),最終迭代產(chǎn)生的最后 16 字節(jié)會(huì)被丟棄,留下 80 字節(jié)作為輸出數(shù)據(jù)。 TLS 的 PRF 可以通過(guò)將 P_hash 運(yùn)用與 secret 來(lái)實(shí)現(xiàn): PRF(secret, label, seed) = P_ label 是一個(gè) ASCII 字符串。它應(yīng)該以嚴(yán)格地按照它被給出的內(nèi)容進(jìn)行處理,不包含一個(gè)長(zhǎng)度字節(jié)或結(jié)尾添加的空字符。例如,label "slithy toves" 應(yīng)該通過(guò) hash 下列字節(jié)的方式被處理: 73 6C 69 74 68 79 20 74 6F 76 65 73 上述數(shù)據(jù)是字符串 "slithy toves" 的十六進(jìn)制格式。 PRF 使用的 Hash 算法取決于密碼套件和 TLS 版本,對(duì)應(yīng)關(guān)系如下: PRF 算法Hash 算法prf_tls10TLS 1.0 和 TLS 1.1 協(xié)議,PRF 算法是結(jié)合 MD5 和 SHA_1 算法prf_tls12_sha256TLS 1.2 協(xié)議,默認(rèn)是 SHA_256 算法(這是能滿足最低安全的算法)prf_tls12_sha384TLS 1.2 協(xié)議,如果加密套件指定的 HMAC 算法安全級(jí)別高于 SHA_256,則采用加密基元 SHA_384 算法 在 TLS 1.0 和 TLS 1.1 中,調(diào)用了兩次 P_HASH,一次是 MD5 一次是 SHA1,兩次的結(jié)果進(jìn)行異或得到最后的結(jié)果。 r1 = P_MD5(...); 在 TLS 1.2 中,PRF 算法其實(shí)就是直接調(diào)用了 P_HASH 算法,默認(rèn)是 SHA_256 算法。 三. TLS 1.2 中的密鑰計(jì)算TLS 1.2 中的密鑰算法主要是上一章談到的 PRF。PRF 主要用于導(dǎo)出主密鑰和會(huì)話密鑰(密鑰塊)的。 1. 計(jì)算主密鑰 為了開(kāi)始連接保護(hù),TLS 記錄協(xié)議要求指定一個(gè)算法套件,一個(gè)主密鑰和 Client 及 Server 端隨機(jī)數(shù)。認(rèn)證,加密和消息認(rèn)證碼算法由 cipher_suite 確定,cipher_suite 是由 Server 選定并在 ServerHello 消息中表明出來(lái)的。壓縮算法在 hello 消息里協(xié)商出來(lái),隨機(jī)數(shù)也在 hello 消息中交換。所有這些都用于計(jì)算主密鑰。 對(duì)于所有的密鑰交換算法,相同的算法都會(huì)被用來(lái)將 pre_master_secret 轉(zhuǎn)化為 master_secret。一旦 master_secret 計(jì)算完畢,pre_master_secret就應(yīng)當(dāng)從內(nèi)存中刪除。避免攻擊者獲取預(yù)備主密鑰,如果攻擊者獲取到了預(yù)備主密鑰,加上 ClientHello.random 和 ServerHello.random 傳輸過(guò)程中是不加密的,也容易獲取,那么攻擊者就可以合成主密鑰并進(jìn)一步導(dǎo)出會(huì)話密鑰,這樣整個(gè)加密過(guò)程就被完全破解了。 master_secret = PRF(pre_master_secret, "master secret", 主密鑰的長(zhǎng)度一直是 48 字節(jié)。預(yù)密鑰的長(zhǎng)度根據(jù)密鑰交換算法而變。 RSA當(dāng)RSA被用于身份認(rèn)證和密鑰交換時(shí),Client 會(huì)產(chǎn)生一個(gè) 48 字節(jié)的 pre_master_secret,用 Server 的公鑰加密,然后發(fā)送給 Server。Server 用它自己的私鑰解密 pre_master_secret。然后雙方按照前述方法將 pre_master_secret轉(zhuǎn)換為 master_secret。 struct { Diffie-Hellman一個(gè)傳統(tǒng)的 Diffie-Hellman 計(jì)算需要被執(zhí)行。協(xié)商出來(lái)的密鑰(Z)會(huì)被用做pre_master_secret,并按照前述方法將其轉(zhuǎn)換為 master_secret。在被用做pre_master_secret之前,Z 開(kāi)頭所有的 0 位都會(huì)被壓縮。 注:Diffie-Hellman 參數(shù)由 Server 指定,可能是臨時(shí)的也可能包含在 Server 的證書(shū)中。 2. 計(jì)算增強(qiáng)型主密鑰在之前的文章中,我們看到了 ClientHello 的擴(kuò)展中攜帶了 extended_master_secret 擴(kuò)展,這個(gè)擴(kuò)展標(biāo)識(shí) Client 和 Server 使用增強(qiáng)型主密鑰計(jì)算方式。 Server 在 ServerHello 中響應(yīng)該擴(kuò)展,返回了一個(gè)空的 extended_master_secret 擴(kuò)展,表明會(huì)使用增強(qiáng)型主密鑰計(jì)算方式。 那么增強(qiáng)型主密鑰是如何計(jì)算的呢?計(jì)算方式如下: master_secret = PRF(pre_master_secret, "extended master secret", 上面的計(jì)算方式和普通計(jì)算主密鑰方式不同點(diǎn)在于:
除了來(lái)自 Client 和 Server 的密碼套件,密鑰交換信息和證書(shū)(如果有的話)之外,"session_hash" 還取決于包括 "ClientHello.random" 和 "ServerHello.random" 的握手日志。因此,擴(kuò)展主密鑰取決于所有這些會(huì)話參數(shù)的選擇。 此設(shè)計(jì)反映了密鑰應(yīng)該綁定到計(jì)算它們的安全上下文的建議 SP800-108。將密鑰交換消息的散列混合到主密鑰導(dǎo)出中的技術(shù)已經(jīng)用于其他眾所周知的協(xié)議,例如 Secure Shell(SSH)RFC4251。Client 和 Server 不應(yīng)接受不使用擴(kuò)展主密鑰的握手,特別是如果它們依賴于復(fù)合認(rèn)證等功能。
3. 計(jì)算會(huì)話密鑰會(huì)話密鑰(密鑰塊)用于 TLS 記錄層加密。記錄協(xié)議需要一個(gè)算法從握手協(xié)議提供的安全參數(shù)中生成當(dāng)前連接狀態(tài)所需的密鑰。 enum { null(0), (255) } CompressionMethod; 主密鑰被擴(kuò)張為一個(gè)安全字節(jié)序列,它被分割為一個(gè) client_write_MAC_key,一個(gè) server_write_MAC_key,一個(gè) client_write_key,一個(gè) server_write_key。它們中的每一個(gè)都是從字節(jié)序列中以上述順序生成。未使用的值是空。一些AEAD加密可能會(huì)額外需要一個(gè) client_write_IV 和一個(gè) server_write_IV。生成密鑰和 MAC 密鑰時(shí),主密鑰被用作一個(gè)熵源。所以會(huì)話密鑰(密鑰塊)的長(zhǎng)度和個(gè)數(shù)取決于協(xié)商出來(lái)的密碼套件,更準(zhǔn)確的說(shuō)是取決于加密參數(shù) SecurityParameters,需要使用 PRF 函數(shù)擴(kuò)展出足夠長(zhǎng)的密鑰塊,計(jì)算如下: key_block = PRF(SecurityParameters.master_secret, 注意:計(jì)算會(huì)話密鑰和主密鑰使用 PRF 的三個(gè)入?yún)⒍疾煌?,PRF(secret, label, seed):主密鑰是 (pre_master_secret, "master secret", ClientHello.random + ServerHello.random),會(huì)話密鑰是 (SecurityParameters.master_secret, "key expansion", SecurityParameters.server_random + SecurityParameters.client_random),seed 順序有變化,Client 和 Server 隨機(jī)數(shù)的組合順序會(huì)調(diào)換。 直到產(chǎn)生足夠的輸出。然后,key_block會(huì)按照如下方式分開(kāi): client_write_MAC_key[SecurityParameters.mac_key_length] client_write_key、server_write_key、client_write_MAC_key 和 server_write_MAC_key 是加密和消息驗(yàn)證碼需要的密鑰。Client 和 Server 分別擁有自己的一套密鑰,使用的密鑰是不同的。如果是分組加密方式,還需要初始化向量 client_write_IV 和 server_write_IV。如果是 AEAD 模式,client_write_MAC_key 和 server_write_MAC_key 可以不需要,使用 client_write_IV 和 server_write_IV 作為 nonce(隨機(jī)值) 。 目前,client_write_IV 和 server_write_IV 只能由 AEAD 的隱式 nonce 技術(shù)生成。 當(dāng)前定義的密碼協(xié)議套件使用最多的是 AES_256_CBC_SHA256。它需要 2 x 32 字節(jié)密鑰和 2 x 32 字節(jié) MAC 密鑰,它們從 128 字節(jié)的密鑰數(shù)據(jù)中產(chǎn)生。 總結(jié) TLS 1.2 密鑰計(jì)算流程如下: 四. TLS 1.2 Finished 校驗(yàn)在 TLS 1.2 握手的最后,會(huì)發(fā)送 Finished 子消息,這條消息是加密的第一條消息,F(xiàn)inished 消息的接收者必須要驗(yàn)證這條消息的內(nèi)容是否正確。驗(yàn)證的內(nèi)容是通過(guò) PRF 算法計(jì)算出來(lái)的。 verify_data = PRF(master_secret, 在計(jì)算 verify_data 的時(shí)候,PRF(secret, label, seed) 中 secret 是主密鑰,label 是 finished_label,Client 是 "client finished",Server 是 "server finished",seed 是所有握手消息的 hash 值。對(duì)于 Client 來(lái)說(shuō),handshake_messages 內(nèi)容包含所有發(fā)送的消息和接收的消息,但是不包括自己發(fā)送的 Finished 消息。對(duì)于 Server 來(lái)說(shuō),handshake_messages 內(nèi)容包含從 ClientHello 消息開(kāi)始截止到 Finished 消息之前的所有消息,也包括 Client 的 Finished 子消息。
早期 TLS 協(xié)議,verify_data 的長(zhǎng)度是 12 字節(jié),對(duì)于 TLS 1.2 協(xié)議來(lái)說(shuō),verify_data 的長(zhǎng)度取決于密鑰套件,如果密碼套件沒(méi)有指定 verify_data_length,則默認(rèn)長(zhǎng)度也是 12 字節(jié)。 五. TLS 1.2 的無(wú)密鑰交換如果 CDN 廠商想支持 HTTPS,那么需要做哪些改動(dòng)呢?國(guó)內(nèi)的廠商的做法是:將自己 HTTPS 網(wǎng)站的私鑰上傳到 CDN 廠商提供的服務(wù)器上。某些對(duì)安全性要求非常高的客戶(比如銀行)想要使用第三方的 CDN,想加快自家網(wǎng)站的訪問(wèn)速度,但是出于安全考慮,不能把私鑰交給 CDN 服務(wù)商。讀者如果已經(jīng)看懂了上面 TLS 的密鑰計(jì)算的方法,完全沒(méi)有必要把私鑰上傳到第三方 CDN 服務(wù)器上。CloudFlare 很早就提供了 Keyless 服務(wù),即你把網(wǎng)站放到它們的 CDN 上,不用提供自己證書(shū)的私鑰,也能使用 TLS/SSL 加密鏈接。 在握手階段,主要是協(xié)商出了 3 個(gè)隨機(jī)數(shù)。這 3 個(gè)隨機(jī)數(shù)產(chǎn)生了 TLS 記錄層需要的會(huì)話密鑰(密鑰塊)。握手完成以后,之后的加密都是對(duì)稱加密。唯一需要用到非對(duì)稱加密中的私鑰。如果是 RSA 密鑰協(xié)商,私鑰的作用是解密 Client 傳過(guò)來(lái)的預(yù)備主密鑰。非對(duì)稱加密中的公鑰用來(lái)加密發(fā)給 Client 的密鑰協(xié)商參數(shù)。但是 Server 的公鑰可以從證書(shū)中獲取。所以 CDN 唯一不能解決的問(wèn)題是解密 Client 發(fā)過(guò)來(lái)的預(yù)備主密鑰。如果是 ECDHE 密鑰協(xié)商,私鑰的作用是對(duì) DH 參數(shù)做簽名的。 解決辦法比較簡(jiǎn)單: 如果是 RSA 密鑰協(xié)商,在 CDN 廠商的服務(wù)器收到 Client 發(fā)來(lái)的預(yù)備主密鑰的時(shí)候,把這個(gè)加密過(guò)的預(yù)備主密鑰發(fā)給用戶自己的 key server,讓用戶用自己的私鑰解密預(yù)備主密鑰,再發(fā)還給 CDN 廠商的服務(wù)器,這樣 CDN 廠商就有解密之后的預(yù)備主密鑰了,進(jìn)而可以繼續(xù)計(jì)算主密鑰和會(huì)話密鑰(密鑰塊)了。流程如下: 如果是 DH 密鑰協(xié)商算法,預(yù)備主密鑰可以由 Server 和 Client 共同計(jì)算出來(lái),但是 DH 相關(guān)的參數(shù)需要雙方協(xié)商出來(lái)。Server 將 DH 相關(guān)參數(shù)發(fā)給 Client 的時(shí)候,需要用到證書(shū)的私鑰。CDN 廠商會(huì)把 Client 隨機(jī)數(shù),Server 隨機(jī)數(shù)和 DH 參數(shù)三者的 hash 發(fā)給用戶的 key server,key server 就它們簽名以后,發(fā)還給 CDN 廠商服務(wù)器。CDN 廠商將簽名后的消息發(fā)給 Client。這樣也就完成了密鑰協(xié)商。CDN 和 Client 相互算出預(yù)備主密鑰和主密鑰還有會(huì)話密鑰。流程如下: 六. TLS 1.3 中的密鑰在 TLS 1.3 中,不再使用 PRF 這種算法了,而是采用更標(biāo)準(zhǔn)的 HKDF 算法來(lái)進(jìn)行密鑰的推導(dǎo)。而且在 TLS 1.3 中對(duì)密鑰進(jìn)行了更細(xì)粒度的優(yōu)化,每個(gè)階段或者方向的加密都不是使用同一個(gè)密鑰。TLS 1.3 在 ServerHello 消息之后的數(shù)據(jù)都是加密的,握手期間 Server 給 Client 發(fā)送的消息用 server_handshake_traffic_secret 通過(guò) HKDF 算法導(dǎo)出的密鑰加密的,Client 發(fā)送給 Server 的握手消息是用 client_handshake_traffic_secret 通過(guò) HKDF 算法導(dǎo)出的密鑰加密的。這兩個(gè)密鑰是通過(guò) Handshake Secret 密鑰來(lái)導(dǎo)出的,而 Handshake Secret 密鑰又是由 PreMasterSecret 和 Early Secret 密鑰導(dǎo)出,然后通過(guò) Handshake Secret 密鑰導(dǎo)出主密鑰 Master Secret。 再由主密鑰 Master Secret 導(dǎo)出這幾個(gè)密鑰: client_application_traffic_secret:用來(lái)導(dǎo)出客戶端發(fā)送給服務(wù)器應(yīng)用數(shù)據(jù)的對(duì)稱加密密鑰。 server_application_traffic_secret:用來(lái)導(dǎo)出服務(wù)器發(fā)送給客戶端應(yīng)用數(shù)據(jù)的對(duì)稱加密密鑰。 resumption_master_secret:用來(lái)生成 PSK。 最終 server_handshake_traffic_secret、client_handshake_traffic_secret、client_application_traffic_secret、server_application_traffic_secret 這 4 個(gè)密鑰會(huì)分別生成 4 套 write_key 和 write_IV 用于對(duì)稱加密。 如果用到 early_data,還需要 client_early_traffic_secret,它也會(huì)生成 1 套 write_key 和 write_IV 用于加密和解密 0-RTT 數(shù)據(jù)。 七. TLS 1.3 中的 HMAC 和偽隨機(jī)函數(shù)Key Derivation Function (KDF) 是密碼學(xué)系統(tǒng)中必要的組件。它的目的是把一個(gè) key 拓展成多個(gè)從密碼學(xué)角度來(lái)上說(shuō)是安全的 key。TLS 1.3 使用的是 HMAC-based Extract-and-Expand Key Derivation Function (HKDF),HKDF 根據(jù) extract-then-expand 設(shè)計(jì)模式,即 KDF 有 2 大模塊。第一個(gè)階段是將輸入的 key material 進(jìn)行 "extracts",得到固定長(zhǎng)度的 key,然后第二階段將這個(gè) key "expands" 成多個(gè)附加的偽隨機(jī)的 key,輸出的 key 的長(zhǎng)度和個(gè)數(shù),取決于指定的加密算法。由于 extract 流程不是必須的,所以 expand 流程可以獨(dú)立的使用。 HMAC 的兩個(gè)參數(shù),第一個(gè)是 key,第二個(gè)是 data。data 可以由好幾個(gè)元素組成,我們一般用 | 來(lái)表示,例如: HMAC(K, elem1 | elem2 | elem3) 1. Extract HKDF-Extract(salt, IKM) -> PRK
PRK 的計(jì)算方法如下: PRK = HMAC-Hash(salt, IKM) HKDF 的定義允許使用有隨機(jī)值 salt 和不帶隨機(jī)值 salt 的操作。這是為了兼容沒(méi)有 salt 的應(yīng)用程序。但是強(qiáng)烈建議使用 salt 能夠顯著加強(qiáng) HKDF 算法的強(qiáng)度。并且確保了哈希函數(shù)的不同用途之間的獨(dú)立性,支持 "源獨(dú)立" extraction,并加強(qiáng)了支持 HKDF 設(shè)計(jì)的分析結(jié)果。 隨機(jī) salt 在兩個(gè)方面與初始密鑰材料 IKM 的根本不同是:它隨機(jī) salt 是非加密的,可以重復(fù)使用。因此,隨機(jī) salt 值可用于許多應(yīng)用。例如,通過(guò)將 HKDF 應(yīng)用于可再生的熵池(例如,采樣系統(tǒng)事件)而連續(xù)產(chǎn)生輸出的偽隨機(jī)數(shù)發(fā)生器(PRNG)可以確定鹽值并將其用于 HKDF 的多個(gè)應(yīng)用而無(wú)需保護(hù)其 salt 的秘密性。在不同的應(yīng)用程序域中,從 Diffie-Hellman 交換中導(dǎo)出加密密鑰的密鑰協(xié)商協(xié)議可以從通信方之間交換和驗(yàn)證的公共 nonce 中獲取 salt 值,并把這種做法作為密鑰協(xié)議的一部分(這是 IKEv2 中采用的方法) 理想情況下,salt 值是長(zhǎng)度為 HashLen 的隨機(jī)(或偽隨機(jī))字符串。然而,即使質(zhì)量較低的 salt 值(較短的尺寸或有限的熵)仍然可能對(duì)輸出密鑰材料的安全性做出重大貢獻(xiàn);因此,如果應(yīng)用程序可以獲得這些值,鼓勵(lì)應(yīng)用程序設(shè)計(jì)者向 HKDF 提供 salt 值。 值得注意的是,雖然不是典型的情況,但某些應(yīng)用甚至可能具有可供使用的加密 salt 值。在這種情況下,HKDF 提供更強(qiáng)大的安全保障。這種應(yīng)用的一個(gè)例子是 IKEv1 在其“公鑰加密模式”中,其中提取器的 salt 是從加密的 nonce 計(jì)算的。類(lèi)似地,IKEv1 的預(yù)共享模式使用從預(yù)共享密鑰導(dǎo)出的加密的 salt。 2. Expand HKDF-Expand(PRK, info, L) -> OKM
OKM 的計(jì)算方法如下: N = ceil(L/HashLen) 雖然 info 值在 HKDF 的定義中是可選的,但它在應(yīng)用程序中通常非常重要。其主要目標(biāo)是將派生的密鑰材料綁定到特定于應(yīng)用程序和上下文的信息。例如,info 可以包含協(xié)議號(hào),算法標(biāo)識(shí)符,用戶身份等。特別地,它可以防止針對(duì)不同的上下文導(dǎo)出相同的密鑰材料(當(dāng)在不同背景下使用相同的輸入密鑰材料(IKM)時(shí))。如果需要,它還可以容納對(duì)密鑰擴(kuò)展部分的附加輸入(例如,應(yīng)用程序可能想要將密鑰材料綁定到其長(zhǎng)度 L,從而使得 info 字段擴(kuò)充至 L 長(zhǎng)度)。info 有一個(gè)技術(shù)要求:它應(yīng)該獨(dú)立于輸入密鑰材料 IKM 的值。 對(duì)比 TLS 1.2 中的 PRF 計(jì)算方法: PRF(secret, label, seed) = P_ 可以看到這兩個(gè)算法的區(qū)別。 在一些應(yīng)用中,輸入密鑰材料 IKM 可能已經(jīng)作為密碼強(qiáng)密鑰的存在(例如,TLS RSA 密碼套件中的預(yù)主密鑰將是偽隨機(jī)字符串,除了前兩個(gè)字節(jié))。在這種情況下,可以跳過(guò) extract 提取部分并在 expand 擴(kuò)展步驟中直接使用 IKM 作為 HMAC 的入?yún)?。另一方面,為了與一般情況兼容,應(yīng)用程序仍然可以使用 extract 提取部分。特別是,如果 IKM 是隨機(jī)(或偽隨機(jī))但長(zhǎng)于 HMAC 密鑰,則 extract 提取步驟可用于輸出合適的 HMAC 密鑰(在 HMAC 的情況下,通過(guò) extractor 提取器的進(jìn)行縮短不是嚴(yán)格必要的,因?yàn)?HMAC 也需要長(zhǎng)度達(dá)到一定程度才能工作)。但是請(qǐng)注意,如果 IKM 是 Diffie-Hellman值,就像使用 Diffie-Hellman 的 TLS 一樣,則不應(yīng)跳過(guò) extract 提取部分。這樣做會(huì)導(dǎo)致使用 Diffie-Hellman 值 g ^ {xy} 本身(不是均勻隨機(jī)或偽隨機(jī)字符串)作為 HMAC 的關(guān)鍵PRK。相反,HKDF 應(yīng)該先將 g ^ {xy} 進(jìn)行 extract 提取步驟(優(yōu)選具有 salt 值的),并把所得的 PRK 作為 HMAC expansion 部分的關(guān)鍵部分。 在所需的密鑰位數(shù) L 不大于 HashLen 的情況下,可以直接使用 PRK 作為 OKM。但是,這不是推薦的,特別是因?yàn)樗鼤?huì)省略使用 info 作為推導(dǎo)過(guò)程的一部分(并且不建議在 extract 提取步驟中添加 info 作為輸入 - 參見(jiàn) HKDF-paper) 在 TLS 1.3 的密鑰派生過(guò)程使用 HMAC-based Extract-and-Expand Key Derivation Function (HKDF) [RFC5869] 定義的 HKDF-Extract 和 HKDF-Expand 函數(shù),以及下面定義的函數(shù): HKDF-Expand-Label(Secret, Label, Context, Length) = Transcript-Hash 和 HKDF 使用的 Hash 函數(shù)是密碼套件哈希算法。Hash.length 是其輸出長(zhǎng)度(以字節(jié)為單位)。消息是表示的握手消息的串聯(lián),包括握手消息類(lèi)型和長(zhǎng)度字段,但不包括記錄層頭。請(qǐng)注意,在某些情況下,零長(zhǎng)度 context(由 "" 表示)傳遞給 HKDF-Expand-Label。labels 都是 ASCII 字符串,不包括尾隨 NUL 字節(jié)。 由上面的函數(shù)調(diào)用關(guān)系,可以得到下面的結(jié)論: Derive-Secret(Secret, Label, Messages) = HKDF-Extract(salt, IKM) 就是 TLS 1.3 中 HKDF 的 Extract 過(guò)程;Derive-Secret(Secret, Label, Messages) 就是 TLS 1.3 中 HKDF 的 Expand 過(guò)程。 3. Transcript-Hash最后再來(lái)談?wù)?Transcript-Hash 函數(shù)。TLS 中的許多加密計(jì)算都使用了哈希副本。這個(gè)值是通過(guò)級(jí)聯(lián)每個(gè)包含的握手消息的方式進(jìn)來(lái)哈希計(jì)算的,它包含握手消息頭部攜帶的握手消息類(lèi)型和長(zhǎng)度字段,但是不包括記錄層的頭部。例如: Transcript-Hash(M1, M2, ... Mn) = Hash(M1 || M2 || ... || Mn) 作為此一般規(guī)則的例外,當(dāng) Server 用一條 HelloRetryRequest 消息來(lái)響應(yīng)一條 ClientHello 消息時(shí),ClientHello1 的值替換為包含 Hash(ClientHello1)的握手類(lèi)型為 "message_hash" 的特殊合成握手消息。例如: Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) = 設(shè)計(jì)這種結(jié)構(gòu)的原因是允許 Server 通過(guò)在 cookie 中僅存儲(chǔ) ClientHello1 的哈希值來(lái)執(zhí)行無(wú)狀態(tài) HelloRetryRequest,而不是要求它導(dǎo)出整個(gè)中間哈希狀態(tài)。 具體而言,哈希副本始終取自于下列握手消息序列,從第一個(gè) ClientHello 開(kāi)始,僅包括已發(fā)送的消息:ClientHello, HelloRetryRequest, ClientHello, ServerHello, EncryptedExtensions, server CertificateRequest, server Certificate, server CertificateVerify, server Finished, EndOfEarlyData, client Certificate, client CertificateVerify, client Finished。 通常上,實(shí)現(xiàn)方可以下面的方法來(lái)實(shí)現(xiàn)哈希副本:根據(jù)協(xié)商的哈希來(lái)維持一個(gè)動(dòng)態(tài)的哈希副本。請(qǐng)注意,隨后的握手后認(rèn)證不會(huì)相互包含,只是通過(guò)主握手結(jié)束的消息。 八. TLS 1.3 中的密鑰計(jì)算經(jīng)過(guò)密鑰協(xié)商得出來(lái)的密鑰材料的隨機(jī)性可能不夠,協(xié)商的過(guò)程能被攻擊者獲知,需要使用一種密鑰導(dǎo)出函數(shù)來(lái)從初始密鑰材料(PSK 或者 DH 密鑰協(xié)商計(jì)算出來(lái)的 key)中獲得安全性更強(qiáng)的密鑰。HKDF 正是 TLS 1.3 中所使用的這樣一個(gè)算法,使用協(xié)商出來(lái)的密鑰材料和握手階段報(bào)文的哈希值作為輸入,可以輸出安全性更強(qiáng)的新密鑰。 從上一章中,我們知道,HKDF 包括 extract_then_expand 的兩階段過(guò)程。extract 過(guò)程增加密鑰材料的隨機(jī)性,在 TLS 1.2 中使用的密鑰導(dǎo)出函數(shù) PRF 實(shí)際上只實(shí)現(xiàn)了 HKDF 的 expand 部分,并沒(méi)有經(jīng)過(guò) extract,而直接假設(shè)密鑰材料的隨機(jī)性已經(jīng)符合要求。 這一章中,讓我們來(lái)看看 TLS 1.3 是如何對(duì)密鑰材料進(jìn)行 extract_then_expand 的。這一章也展示了 TLS 1.3 比 TLS 1.2 在安全性上更上一層樓的原因。 TLS 1.3 中的所有密鑰都是由 HKDF-Extract(salt, IKM) 和 Derive-Secret(Secret, Label, Messages) 聯(lián)合導(dǎo)出的。其中 Salt 是當(dāng)前的 secret 狀態(tài),輸入密鑰材料(IKM)是要添加的新 secret 。在 TLS 1.3 中,兩個(gè)輸入的 IKM 是:
TLS 1.3 完整的密鑰導(dǎo)出流程圖如下: 0 幾點(diǎn)說(shuō)明:
如果給定的 secret 不可用,則使用由設(shè)置為零的 Hash.length 字節(jié)串組成的 0 值。請(qǐng)注意,這并不意味著要跳過(guò)輪次,因此如果 PSK 未被使用,Early Secret 仍將是 HKDF-Extract(0,0)。對(duì)于 binder_key 的計(jì)算,label 是外部 PSK(在 TLS 之外提供的那些)的 "ext binder" 和用于恢復(fù) PSK 的 "res binder"(提供為先前握手的恢復(fù)主密鑰的那些)。不同的 labels 阻止了一種 PSK 替代另一種 PSK。 這存在有多個(gè)潛在的 Early Secret 值,具體取決于 Server 最終選擇的 PSK。Client 需要為每個(gè)潛在的 PSK 都計(jì)算一個(gè)值;如果沒(méi)有選擇 PSK,則需要計(jì)算對(duì)應(yīng)于零 PSK 的 Early Secret。 一旦計(jì)算出了從給定 secret 派生出的所有值,就應(yīng)該刪除該 secret。 TLS 1.3 中涉及到了 3 個(gè) Secret 計(jì)算方法如下: Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) TLS 1.3 中涉及到了 8 個(gè)密鑰計(jì)算方法如下: client_early_traffic_secret = Derive-Secret(Early Secret, "c e traffic", ClientHello) 例如: CLIENT_EARLY_TRAFFIC_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 5a0d40c3afa57cbb5aa427456f8dc21b9c4c17bfb731600f93e35358f5b581cb EXPORTER_SECRET 是導(dǎo)出密鑰,用于用戶自定義的其他用途。 上面得到的 8 個(gè)密鑰除去 2 個(gè)用戶自定義需要的導(dǎo)出密鑰,和會(huì)話恢復(fù)的 resumption_master_secret,剩下的 5 個(gè)密鑰雖然是經(jīng)過(guò)一次 HKDF 的 Expand 過(guò)程,但是這 5 個(gè)密鑰仍然只是“中間變量”,生成最后的加密參數(shù)還需要一次 Expand 過(guò)程: [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) [sender] 表示發(fā)送方。每種記錄類(lèi)型的 Secret 值顯示在下表中: +-------------------+---------------------------------------+ 每當(dāng)?shù)讓?Secret 更改時(shí)(例如,從握手更改為應(yīng)用數(shù)據(jù)密鑰或密鑰更新時(shí)),將重新計(jì)算所有流量密鑰材料。 resumption_master_secret 密鑰是為了會(huì)話恢復(fù)導(dǎo)出 PSK 的,計(jì)算方法如下: PskIdentity.identity = ticket Server 在 NewSessionTicket 中把 ticket 發(fā)送到 Client,Client 利用 ticket 生成 PskIdentity。再計(jì)算 PskBinderEntry: PskBinderEntry = HMAC(binder_key, Transcript-Hash(Truncate(ClientHello1))) Client 將 PskIdentity 和 PskBinderEntry 結(jié)合成 PSK,在需要會(huì)話恢復(fù)的時(shí)候把 PSK 作為 ClientHello 的擴(kuò)展發(fā)給 Server。PSK 作為 Early Secret 的輸入密鑰材料 IKM。 Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) 由 client_early_traffic_secret 生成的 write_key 和 write_iv 最終用于 0-RTT 的加密和解密。 TLS 1.3 0-RTT 密鑰計(jì)算流程如下: 九. TLS 1.3 Finished 校驗(yàn)TLS 1.3 中的 Finished 并不算是整個(gè)握手中的第一條加密消息,作用和 TLS 1.2 是相同的,它對(duì)提供握手和計(jì)算密鑰的身份驗(yàn)證起了至關(guān)重要的作用。 在 TLS 1.3 中 Authentication 消息的計(jì)算統(tǒng)一采用以下的輸入方式:
Finished 子消息根據(jù) Transcript-Hash(Handshake Context, Certificate, CertificateVerify) 的值得出的 MAC 。使用從 Base key 派生出來(lái)的 MAC key 計(jì)算的 MAC 值。 對(duì)于每個(gè)場(chǎng)景,下表定義了握手上下文和 MAC Base Key +-----------+-------------------------+-----------------------------+ 用于計(jì)算 Finished 消息的密鑰是使用 HKDF,Base Key 是 server_handshake_traffic_ secret 和 client_handshake_traffic_secret。特別的: finished_key = 這條消息的數(shù)據(jù)結(jié)構(gòu)是: struct { verify_data 按照如下方法計(jì)算: verify_data = HMAC [RFC2104] 使用哈希算法進(jìn)行握手。如上所述,HMAC 輸入通常是通過(guò)動(dòng)態(tài)的哈希實(shí)現(xiàn)的,即,此時(shí)僅是握手的哈希。 在以前版本的 TLS 中,verify_data 的長(zhǎng)度總是 12 個(gè)八位字節(jié)。在 TLS 1.3 中,它是用來(lái)表示握手的哈希的 HMAC 輸出的大小。 注意:警報(bào)和任何其他非握手記錄類(lèi)型不是握手消息,并且不包含在哈希計(jì)算中。 Finished 消息之后的任何記錄 Post-Handshake 都必須在適當(dāng)?shù)?client_application_traffic_secret_N 下加密。特別是,這包括 Server 為了響應(yīng) Client 的 Certificate 消息和 CertificateVerify 消息而發(fā)送的任何 alert。 十. TLS 1.3 KeyUpdate看到這里讀者可能會(huì)問(wèn),為什么在文章最后還會(huì)再討論 TLS 1.3 的 KeyUpdate 消息?因?yàn)檫@條消息會(huì)觸發(fā) TLS 1.3 重新計(jì)算密鑰。所以需要細(xì)究一下這條消息。 研究表明 如果使用同一個(gè)密鑰加密大量的數(shù)據(jù),攻擊者有幾率可以通過(guò)記錄所有密文并找出特征,逆推出對(duì)稱加密密鑰。因此需要引進(jìn)一個(gè)密鑰同步更新的機(jī)制,該機(jī)制同時(shí)也使用 HKDF 算法,在舊密鑰的基礎(chǔ)上衍生出新一輪的密鑰。 當(dāng)加密的報(bào)文達(dá)到一定長(zhǎng)度后,雙方也需要發(fā)送 KeyUpdate 報(bào)文重新計(jì)算加密密鑰。 KeyUpdate 握手消息用于表示發(fā)送方正在更新其自己的發(fā)送加密密鑰。任何對(duì)等方在發(fā)送 Finished 消息后都可以發(fā)送此消息。在接收 Finished 消息之前接收 KeyUpdate 消息的,實(shí)現(xiàn)方必須使用 "unexpected_message" alert 消息終止連接。發(fā)送 KeyUpdate 消息后,發(fā)送方應(yīng)使用新一代的密鑰發(fā)送其所有流量。收到 KeyUpdate 后,接收方必須更新其接收密鑰。 enum {
如果 request_update 字段設(shè)置為 "update_requested",則接收方必須在發(fā)送其下一個(gè)應(yīng)用數(shù)據(jù)記錄之前發(fā)送自己的 KeyUpdate,其中 request_update 設(shè)置為 "update_not_requested"。此機(jī)制允許任何一方強(qiáng)制更新整個(gè)連接,但會(huì)導(dǎo)致一個(gè)實(shí)現(xiàn)方接收多個(gè) KeyUpdates,并且它還是靜默的響應(yīng)單個(gè)更新。請(qǐng)注意,實(shí)現(xiàn)方可能在發(fā)送 KeyUpdate (把 request_update 設(shè)置為 "update_requested") 與接收對(duì)等方的 KeyUpdate 之間接收任意數(shù)量的消息,因?yàn)檫@些消息可能早就已經(jīng)在傳輸中了。但是,由于發(fā)送和接收密鑰是從獨(dú)立的流量密鑰中導(dǎo)出的,因此保留接收流量密鑰并不會(huì)影響到發(fā)送方更改密鑰之前發(fā)送的數(shù)據(jù)的前向保密性。 如果實(shí)現(xiàn)方獨(dú)立地發(fā)送它們自己的 KeyUpdates,其 request_update 設(shè)置為 "update_requested" 并且它們的消息都是傳輸中,結(jié)果是雙方都會(huì)響應(yīng),雙方都會(huì)更新密鑰。 發(fā)送方和接收方都必須使用舊密鑰加密其 KeyUpdate 消息。另外,在接受使用新密鑰加密的任何消息之前,雙方必須強(qiáng)制接收帶有舊密鑰的 KeyUpdate。如果不這樣做,可能會(huì)引起消息截?cái)喙簟?/p> 下一代流量密鑰的計(jì)算方法是,從 client_ / server_application_traffic_secret_N 生成出 client_ / server_application_traffic_secret_N + 1,然后按上一節(jié)所述方法重新導(dǎo)出流量密鑰。 下一代 application_traffic_secret 計(jì)算方法如下: application_traffic_secret_N+1 = 一旦計(jì)算了 client_ / server_application_traffic_secret_N + 1 及其關(guān)聯(lián)的流量密鑰,實(shí)現(xiàn)方應(yīng)該刪除 client_ / server_application_traffic_secret_N 及其關(guān)聯(lián)的流量密鑰。 十一. TLS 1.3 中的密鑰導(dǎo)出在 TLS 1.3 中,有 2 個(gè)導(dǎo)出密鑰 exporter: early_exporter_master_secret = Derive-Secret(Early Secret, "e exp master", ClientHello) RFC5705 根據(jù) TLS 偽隨機(jī)函數(shù)(PRF)定義 TLS 的密鑰材料 exporter。TLS 1.3 用 HKDF 取代 PRF,因此需要新的結(jié)構(gòu)。exporter 的接口保持不變。 exporter 的值計(jì)算方法如下: TLS-Exporter(label, context_value, key_length) = Secret 可以是 early_exporter_master_secret 或 exporter_master_secret。除非應(yīng)用程序明確指定,否則實(shí)現(xiàn)方必須使用 exporter_master_secret。early_exporter_master_secret 被定義用來(lái)在 0-RTT 數(shù)據(jù)需要 exporter 的設(shè)置這種情況中使用。建議為 early exporter 提供單獨(dú)的接口;這可以避免 exporter 用戶在需要常規(guī) exporter 時(shí)意外使用 early exporter,反之亦然。 如果未提供上下文,則 context_value 為零長(zhǎng)度。因此,不提供上下文計(jì)算與提供空上下文得到的結(jié)果都是相同的。這是對(duì)以前版本的 TLS 的更改,以前的 TLS 版本中,空的上下文產(chǎn)生的輸出與不提供的上下文的結(jié)果不同。截至 TLS 1.3,無(wú)論是否使用上下文,都不會(huì)使用已分配的 exporter 標(biāo)簽。未來(lái)的規(guī)范絕不能定義允許空上下文和沒(méi)有相同標(biāo)簽的上下文的 exporter 的使用。exporter 的新用法應(yīng)該是在所有 exporter 計(jì)算中提供上下文,盡管值可能為空。 exporter 標(biāo)簽格式的要求在 [RFC5705] 第4節(jié) 中定義。 Reference:RFC 5246 RFC 8466 Keyless SSL: The Nitty Gritty Technical Details Cryptographic Extraction and Key Derivation: The HKDF Scheme
|