AES-GCM-HKDF 串流 AEAD

本文件正式定義 AES-GCM-HKDF 串流金鑰所代表的數學函式,並以 proto 格式編碼為 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey

這項加密機制大致以 HRRV151 為基礎。如需安全性分析,請參閱 HS20 2

鍵和參數

金鑰會以以下部分說明 (本文件中的所有大小皆以位元組為單位):

  • \(\mathrm{KeyValue}\),位元組字串。
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\)。
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\)。
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\)。

有效的鍵還必須符合下列屬性:

  • \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\)。
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (這等於 \(\mathrm{len}(\mathrm{Header}) + 16\) ,稍後會說明)。

不符合上述任何屬性的鍵,會在剖析鍵或建立對應的原始元素時遭到 Tink 拒絕。

加密功能

如要加密含有相關資料的訊息 \(\mathrm{Msg}\) \(\mathrm{AssociatedData}\),我們會建立標頭、將訊息分割成多個區段、加密每個區段,然後將加密的區段連結在一起。

建立標頭

我們會選取長度為\(\mathrm{DerivedKeySize}\) 的均一隨機字串 \(\mathrm{Salt}\) ,以及長度為 7 的均一隨機字串 \(\mathrm{NoncePrefix}\)。

接著,我們會設定 \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\),其中標頭長度會編碼為單一位元組。請注意 \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\)。

接下來,我們會使用 HKDF3,並搭配 \(\mathrm{HkdfHashType}\)和輸入值 \(\mathrm{ikm} := \mathrm{KeyValue}\)、 \(\mathrm{salt} := \mathrm{Salt}\)和 \(\mathrm{info} := \mathrm{AssociatedData}\)提供的雜湊函式,以及輸出長度 \(\mathrm{DerivedKeySize}\)。我們將結果稱為 \(\mathrm{DerivedKey}\)。

分割訊息

接著,訊息 \(\mathrm{Msg}\) 會分成以下部分: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\)。

長度會根據下列條件選擇:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\)。
  • 如果為 \(n>1\),則為 \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\)。
  • 如果是 \(n>1\),則 \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) 必須根據上述限制設為長度上限。

\(n\) 的長度不得超過 \(2^{32}\)。否則加密作業會失敗。

加密區塊

為了加密區段 \(M_i\),我們會計算 \(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b\),其中 \(\mathrm{i}\) 是 4 個大端序編碼位元組,如果 $i < n-1$,位元組 $b$ 為 0x00,否則為 0x01

接著,我們使用 AES-GCM 加密 \(M_i\) 4,其中金鑰為\(\mathrm{DerivedKey}\)、初始化向量為 \(\mathrm{IV}_i\),而相關聯資料為空字串。 \(C_i\) 是這項加密的結果 (即已連結 AES-GCM 參考資料的 5.2.1.2 節中 \(C\) 和 \(T\) 的連接串連)。

連接加密的區段

最後,所有區段會串連為 \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\),這就是最終的密文。

解密

解密則是將加密內容反轉。我們會使用標頭取得\(\mathrm{NoncePrefix}\),然後個別解密每個密文區段。

API 可能 (通常會) 允許隨機存取,或在未檢查檔案結尾的情況下存取檔案開頭。這是刻意設計的,因為您可以從 \(C_i\)解密 \(M_i\) ,而無須解密所有先前和剩餘的密文區塊。

不過,API 應謹慎處理,避免使用者將檔案結尾和解密錯誤搞混:在兩種情況下,API 都可能會傳回錯誤,而忽略差異可能導致對手有效截斷檔案。

金鑰的序列化和剖析

如要以「Tink Proto」格式序列化鍵,我們會先以明顯的方式將參數對應至 aes_gcm_hkdf_streaming.proto 提供的原始檔。version 欄位必須設為 0。接著,我們會使用一般 Proto 序列化功能將其序列化,並將產生的字串嵌入 KeyData Proto 欄位的值中。我們將 type_url 欄位設為 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey。接著,我們將 key_material_type 設為 SYMMETRIC,並將其嵌入鍵組中。我們通常會將 output_prefix_type 設為 RAW。例外狀況是,如果索引鍵是以 output_prefix_type 的不同值集進行剖析,Tink 可能會寫入 RAW 或先前的值。

如要剖析鍵,我們會反向執行上述程序 (以剖析 Protobuf 的一般方式)。系統會忽略欄位 key_material_typeoutput_prefix_type 的值可以忽略,或是拒絕 output_prefix_typeRAW 不同的鍵。必須拒絕 version 與 0 不同的鍵。

已知問題

上述加密函式的實作方式不應是分支安全的。請參閱分支安全性

參考資料


  1. Hoang, Reyhanitabar, Rogaway, Vizar, 2015. 線上驗證加密功能,以及其不重複使用的 Nonce 抵禦濫用行為。CRYPTO 2015。https://eprint.iacr.org/2015/189 

  2. Hoang, Shen, 2020. Google Tink 程式庫中的串流加密安全性。https://eprint.iacr.org/2020/1019 

  3. RFC 5869。HMAC Extractor-and-Expand 金鑰衍生函式 (HKDF)。 https://www.rfc-editor.org/rfc/rfc5869 

  4. NIST SP 800-38D。建議的區塊密碼運作模式:Galois/計數器模式 (GCM) 和 GMAC。https://csrc.nist.gov/pubs/sp/800/38/d/final