亚洲成在人线在线播放无码vr|亚洲成熟女同—区二区三区|日韩精品一区二区中文在线|97欧美精品一区二区三区

  • <strike id="14xru"></strike>
  • <object id="14xru"></object>

  • <th id="14xru"></th>
      <strike id="14xru"><video id="14xru"></video></strike>
      1. 湖北企業(yè)新聞網(wǎng),歡迎您!

        幫助中心 廣告聯(lián)系

        網(wǎng)站關(guān)鍵詞: 湖北企業(yè)新聞網(wǎng)

        HTTPS 溫故知新(五)——TLS 中的密鑰計(jì)算

        來(lái)源:時(shí)間:2020-09-03 07:17:08 閱讀:-

        本篇文章我們來(lái)對(duì)比對(duì)比 TLS 1.2 和 TLS 1.3 中的密鑰計(jì)算。

        一. TLS 1.2 中的密鑰

        在 TLS 1.2 中,有 3 種密鑰:預(yù)備主密鑰、主密鑰和會(huì)話密鑰(密鑰塊),這幾個(gè)密鑰都是有聯(lián)系的。

         struct {
        uint32 gmt_unix_time;
        opaque random_bytes[28];
        } Random;

        struct {
        ProtocolVersion client_version;
        opaque random[46];
        } PreMasterSecret;
        struct {
        uint8 major;
        uint8 minor;
        } ProtocolVersion;

        對(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) +
        HMAC_hash(secret, A(2) + seed) +
        HMAC_hash(secret, A(3) + seed) + ...

        這里"+"是指級(jí)聯(lián)。

        A()被定義為:

         A(0) = seed
        A(i) = HMAC_hash(secret, A(i-1))

        必要時(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_(secret, label + seed)

        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(...);
        r2 = P_SHA1(...);
        r = r1 xor r2

        在 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",
        ClientHello.random + ServerHello.random)
        [0..47];

        主密鑰的長(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 {
        ProtocolVersion client_version;
        opaque random[46];
        } PreMasterSecret;

        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",
        session_hash)
        [0..47];

        上面的計(jì)算方式和普通計(jì)算主密鑰方式不同點(diǎn)在于:

        • "extended master secret" 替代了 "master secret"
        • session_hash 替代了 ClientHello.random + ServerHello.random

        除了來(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)證等功能。

        對(duì)這塊攻擊感興趣的讀者可以看這篇文章 《Triple Handshake Preconditions and Impact》

        3. 計(jì)算會(huì)話密鑰

        會(huì)話密鑰(密鑰塊)用于 TLS 記錄層加密。記錄協(xié)議需要一個(gè)算法從握手協(xié)議提供的安全參數(shù)中生成當(dāng)前連接狀態(tài)所需的密鑰。

         enum { null(0), (255) } CompressionMethod;
        enum { server, client } ConnectionEnd;
        enum { tls_prf_sha256 } PRFAlgorithm;
        enum { null, rc4, 3des, aes } BulkCipherAlgorithm;
        enum { stream, block, aead } CipherType;
        enum { null, hmac_md5, hmac_sha1, hmac_sha256, hmac_sha384,
        hmac_sha512} MACAlgorithm;
        /* Other values may be added to the algorithms specified in
        CompressionMethod, PRFAlgorithm, BulkCipherAlgorithm, and
        MACAlgorithm. */
        struct {
        ConnectionEnd entity;
        PRFAlgorithm prf_algorithm;
        BulkCipherAlgorithm bulk_cipher_algorithm;
        CipherType cipher_type;
        uint8 enc_key_length;
        uint8 block_length;
        uint8 fixed_iv_length;
        uint8 record_iv_length;
        MACAlgorithm mac_algorithm;
        uint8 mac_length;
        uint8 mac_key_length;
        CompressionMethod compression_algorithm;
        opaque master_secret[48];
        opaque client_random[32];
        opaque server_random[32];
        } SecurityParameters;

        主密鑰被擴(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,
        "key expansion",
        SecurityParameters.server_random +
        SecurityParameters.client_random);

        注意:計(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]
        server_write_MAC_key[SecurityParameters.mac_key_length]
        client_write_key[SecurityParameters.enc_key_length]
        server_write_key[SecurityParameters.enc_key_length]
        client_write_IV[SecurityParameters.fixed_iv_length]
        server_write_IV[SecurityParameters.fixed_iv_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ì)算流程如下:


        HTTPS 溫故知新(五)——TLS 中的密鑰計(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, 
        finished_label,
        Hash(handshake_messages))
        [0..verify_data_length-1];

        在計(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 子消息。

        handshake_messages 中只包含握手子消息,不包括 ChangeCipherSpec 子消息、 Alert 子消息、HelloRequest 消息。

        早期 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ì)話密鑰(密鑰塊)了。流程如下:


        HTTPS 溫故知新(五)——TLS 中的密鑰計(jì)算


        如果是 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ì)話密鑰。流程如下:


        HTTPS 溫故知新(五)——TLS 中的密鑰計(jì)算


        六. 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。


        HTTPS 溫故知新(五)——TLS 中的密鑰計(jì)算


        再由主密鑰 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
        • 變量:
        • Hash 是 hash 函數(shù); HashLen 表示這個(gè) hash 函數(shù)的輸出字節(jié)數(shù)。
        • 輸入:
        • salt 是可選的值,如果沒(méi)有指定,則使用 HashLen 個(gè) 0 代替。
        • IKM 是輸入的 keying material,IKM 是 Input Keying Material 的縮寫(xiě)。
        • 輸出:
        • PRK 是一個(gè) pseudorandom 偽隨機(jī)的 key (HashLen 字節(jié)大小),PSK 是 PseudoRandom Key 的縮寫(xiě)。

        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ì)算的。類似地,IKEv1 的預(yù)共享模式使用從預(yù)共享密鑰導(dǎo)出的加密的 salt。

        2. Expand

         HKDF-Expand(PRK, info, L) -> OKM
        • 變量:
        • Hash 是 hash 函數(shù); HashLen 表示這個(gè) hash 函數(shù)的輸出字節(jié)數(shù)。
        • 輸入:
        • PRK 是至少 HashLen 字節(jié)長(zhǎng)度的 pseudorandom key (通常由 extract 流程導(dǎo)出)。
        • info 是可選的值,可以是""。
        • L 是期望輸出的字節(jié)數(shù)(長(zhǎng)度 <= 255 * HashLen)。
        • 輸出:
        • OKM 是輸出的 keying material (L 字節(jié)),OKM 是 Output Keying Material 的縮寫(xiě)。

        OKM 的計(jì)算方法如下:

         N = ceil(L/HashLen)
        T = T(1) | T(2) | T(3) | ... | T(N)
        OKM = first L octets of T
        where:
        T(0) = empty string (zero length)
        T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
        T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
        T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
        ...

        雖然 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_(secret, label + seed)
        P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
        HMAC_hash(secret, A(2) + seed) +
        HMAC_hash(secret, A(3) + seed) + ...

        where:
        A(0) = seed
        A(i) = HMAC_hash(secret, A(i-1))
        ...

        可以看到這兩個(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) =
        HKDF-Expand(Secret, HkdfLabel, Length)
        Where HkdfLabel is specified as:
        struct {
        uint16 length = Length;
        opaque label<7..255> = "tls13 " + Label;
        opaque context<0..255> = Context;
        } HkdfLabel;
        Derive-Secret(Secret, Label, Messages) =
        HKDF-Expand-Label(Secret, Label,
        Transcript-Hash(Messages), Hash.length)

        Transcript-Hash 和 HKDF 使用的 Hash 函數(shù)是密碼套件哈希算法。Hash.length 是其輸出長(zhǎng)度(以字節(jié)為單位)。消息是表示的握手消息的串聯(lián),包括握手消息類型和長(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-Expand(Secret, HkdfLabel, Length)

        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ì)算的,它包含握手消息頭部攜帶的握手消息類型和長(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)的握手類型為 "message_hash" 的特殊合成握手消息。例如:

         Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
        Hash(message_hash || /* Handshake type */
        00 00 Hash.length || /* Handshake message length (bytes) */
        Hash(ClientHello1) || /* Hash of 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 是:

        • PSK(外部建立的預(yù)共享密鑰,或從先前連接的 resumption_master_secret 值派生的)
        • (EC)DHE 共享 secret

        TLS 1.3 完整的密鑰導(dǎo)出流程圖如下:

         0
        |
        v
        PSK -> HKDF-Extract = Early Secret
        |
        +-----> Derive-Secret(., "ext binder" | "res binder", "")
        | = binder_key
        |
        +-----> Derive-Secret(., "c e traffic", ClientHello)
        | = client_early_traffic_secret
        |
        +-----> Derive-Secret(., "e exp master", ClientHello)
        | = early_exporter_master_secret
        v
        Derive-Secret(., "derived", "")
        |
        v
        (EC)DHE -> HKDF-Extract = Handshake Secret
        |
        +-----> Derive-Secret(., "c hs traffic",
        | ClientHello...ServerHello)
        | = client_handshake_traffic_secret
        |
        +-----> Derive-Secret(., "s hs traffic",
        | ClientHello...ServerHello)
        | = server_handshake_traffic_secret
        v
        Derive-Secret(., "derived", "")
        |
        v
        0 -> HKDF-Extract = Master Secret
        |
        +-----> Derive-Secret(., "c ap traffic",
        | ClientHello...server Finished)
        | = client_application_traffic_secret_0
        |
        +-----> Derive-Secret(., "s ap traffic",
        | ClientHello...server Finished)
        | = server_application_traffic_secret_0
        |
        +-----> Derive-Secret(., "exp master",
        | ClientHello...server Finished)
        | = exporter_master_secret
        |
        +-----> Derive-Secret(., "res master",
        ClientHello...client Finished)
        = resumption_master_secret

        幾點(diǎn)說(shuō)明:

        1. HKDF-Extract 畫(huà)在圖上,它為從頂部獲取 Salt 參數(shù),從左側(cè)獲取 IKM 參數(shù),它的輸出是底部,和右側(cè)輸出的名稱。
        2. Derive-Secret 的 Secret 參數(shù)由傳入的箭頭指示。例如,Early Secret 是生成 client_early_traffic_secret 的 Secret。
        3. "0" 表示將 Hash.length 字節(jié)的字符串設(shè)置為零。

        如果給定的 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)
        Handshake Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Early Secret, "derived", ""), (EC)DHE)
        Master Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Handshake Secret, "derived", ""), 0)

        TLS 1.3 中涉及到了 8 個(gè)密鑰計(jì)算方法如下:

         client_early_traffic_secret = Derive-Secret(Early Secret, "c e traffic", ClientHello)
        early_exporter_master_secret = Derive-Secret(Early Secret, "e exp master", ClientHello)

        client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "c hs traffic", ClientHello...ServerHello)
        server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "s hs traffic", ClientHello...ServerHello)

        client_application_traffic_secret_0 = Derive-Secret(Master Secret, "c ap traffic", ClientHello...server Finished)
        server_application_traffic_secret_0 = Derive-Secret(Master Secret, "s ap traffic", ClientHello...server Finished)
        exporter_master_secret = Derive-Secret(Master Secret, "exp master", ClientHello...server Finished)
        resumption_master_secret = Derive-Secret(Master Secret, "res master", ClientHello...client Finished)

        例如:

        CLIENT_EARLY_TRAFFIC_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 5a0d40c3afa57cbb5aa427456f8dc21b9c4c17bfb731600f93e35358f5b581cb
        EARLY_EXPORTER_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 274e61024f88d0952898889a54211200a76456434d8e546cd6450f8313412df5
        CLIENT_HANDSHAKE_TRAFFIC_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 c041776dc29543e87e3442111be79f289062eef7603ec566f28f5b05b15c9718
        SERVER_HANDSHAKE_TRAFFIC_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 68e19a5d69dfdf8ca701a370cfd7c21e98b1bd933c03ee9dd72738e60147e8db
        CLIENT_TRAFFIC_SECRET_0 edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 b866b25bc12f5272dbc6d27471edce47d04f496362b56800d5f95e0760d044ee
        SERVER_TRAFFIC_SECRET_0 edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 8f07b32b6191019bac664d5071dd961e92ff2060db629d4e3eb3689a43cc71d3
        EXPORTER_SECRET edb6c73462794c0fe79296853fd17b06cd30e63e87e69c8864eba6996e5d9434 c7a1fb9092f245a8b92cd7a481eb0bd6d255b4d06c6d05096ef8a8bf3face22e

        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]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)

        [sender] 表示發(fā)送方。每種記錄類型的 Secret 值顯示在下表中:

         +-------------------+---------------------------------------+
        | Record Type | Secret |
        +-------------------+---------------------------------------+
        | 0-RTT Application | client_early_traffic_secret |
        | | |
        | Handshake | [sender]_handshake_traffic_secret |
        | | |
        | Application Data | [sender]_application_traffic_secret_N |
        +-------------------+---------------------------------------+

        每當(dāng)?shù)讓?Secret 更改時(shí)(例如,從握手更改為應(yīng)用數(shù)據(jù)密鑰或密鑰更新時(shí)),將重新計(jì)算所有流量密鑰材料。

        resumption_master_secret 密鑰是為了會(huì)話恢復(fù)導(dǎo)出 PSK 的,計(jì)算方法如下:

         PskIdentity.identity = ticket 
        = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)

        Server 在 NewSessionTicket 中把 ticket 發(fā)送到 Client,Client 利用 ticket 生成 PskIdentity。再計(jì)算 PskBinderEntry:

         PskBinderEntry = HMAC(binder_key, Transcript-Hash(Truncate(ClientHello1)))
        = HMAC(Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", ""), Transcript-Hash(Truncate(ClientHello1)))

        其中 binder_key = Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", "")

        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 = Derive-Secret(Early Secret, "c e traffic", ClientHello)

        由 client_early_traffic_secret 生成的 write_key 和 write_iv 最終用于 0-RTT 的加密和解密。

        TLS 1.3 0-RTT 密鑰計(jì)算流程如下:


        HTTPS 溫故知新(五)——TLS 中的密鑰計(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)一采用以下的輸入方式:

        • 要使用證書(shū)和簽名密鑰
        • 握手上下文由哈希副本中的一段消息集組成
        • Base key 用于計(jì)算 MAC 密鑰

        Finished 子消息根據(jù) Transcript-Hash(Handshake Context, Certificate, CertificateVerify) 的值得出的 MAC 。使用從 Base key 派生出來(lái)的 MAC key 計(jì)算的 MAC 值。

        對(duì)于每個(gè)場(chǎng)景,下表定義了握手上下文和 MAC Base Key

         +-----------+-------------------------+-----------------------------+
        | Mode | Handshake Context | Base Key |
        +-----------+-------------------------+-----------------------------+
        | Server | ClientHello ... later | server_handshake_traffic_ |
        | | of EncryptedExtensions/ | secret |
        | | CertificateRequest | |
        | | | |
        | Client | ClientHello ... later | client_handshake_traffic_ |
        | | of server | secret |
        | | Finished/EndOfEarlyData | |
        | | | |
        | Post- | ClientHello ... client | client_application_traffic_ |
        | Handshake | Finished + | secret_N |
        | | CertificateRequest | |
        +-----------+-------------------------+-----------------------------+

        用于計(jì)算 Finished 消息的密鑰是使用 HKDF,Base Key 是 server_handshake_traffic_ secret 和 client_handshake_traffic_secret。特別的:

         finished_key =
        HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)

        這條消息的數(shù)據(jù)結(jié)構(gòu)是:

         struct {
        opaque verify_data[Hash.length];
        } Finished;

        verify_data 按照如下方法計(jì)算:

         verify_data =
        HMAC(finished_key,
        Transcript-Hash(Handshake Context,
        Certificate*, CertificateVerify*))
        * Only included if present.

        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)和任何其他非握手記錄類型不是握手消息,并且不包含在哈希計(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 {
        update_not_requested(0), update_requested(1), (255)
        } KeyUpdateRequest;
        struct {
        KeyUpdateRequest request_update;
        } KeyUpdate;
        • request_update:
        • 這個(gè)字段表示 KeyUpdate 的收件人是否應(yīng)使用自己的 KeyUpdate 進(jìn)行響應(yīng)。 如果實(shí)現(xiàn)接收到任何其他的值,則必須使用 "illegal_parameter" alert 消息終止連接。

        如果 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 =
        HKDF-Expand-Label(application_traffic_secret_N,
        "traffic upd", "", Hash.length)

        一旦計(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)
        exporter_master_secret = Derive-Secret(Master Secret, "exp master", ClientHello...server Finished)

        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) =
        HKDF-Expand-Label(Derive-Secret(Secret, label, ""),
        "exporter", Hash(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

        GitHub Repo:Halfrost-Field

        Follow: halfrost · GitHub

        Source: https://halfrost.com/HTTPS-key-cipher/

        推薦閱讀:蘭州都市網(wǎng)