AES-GCM-HKDF 串流 AEAD
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
本文件正式定義了 AES-GCM-HKDF 串流金鑰表示的數學函式,以 proto 格式編碼為 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
。
這種加密方式大部分是以 HRRV151 為基礎。在進行安全性分析時,我們使用 HS202。
鍵與參數
索引鍵分為下列幾個部分 (本文件中的所有大小均以位元組為單位):
- \(\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{Salt}\) 長度\(\mathrm{DerivedKeySize}\) 的統一隨機字串,以及長度為 7 的統一隨機字串 \(\mathrm{NoncePrefix}\)。
接著設定 \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt}
\| \mathrm{NoncePrefix}\),將標頭長度編碼為單位元組。請注意, \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\)。
接著,使用 HKDF3 搭配 \(\mathrm{HkdfHashType}\)、input \(\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$ 和 0x01
則使用 0x00
位元組 $b$。
然後我們加密 \(M_i\) 使用 AES-GCM4 ,其中的關鍵是\(\mathrm{DerivedKey}\),初始化向量是 \(\mathrm{IV}_i\),且關聯資料為空字串。 \(C_i\) 是這種加密的結果(即串聯 \(C\) 和 \(T\) 在連結的 AES-GCM 參考的第 5.2.1.2 節中)。
串連加密區段
最後,所有區段都會串連為 \(\mathrm{Header} \| C_0 \| \cdots \|
C_{n-1}\),也就是最終的密文。
解密
解密會反向解密加密。我們會使用標頭取得\(\mathrm{NoncePrefix}\),並分別解密每個密文區段。
API 能夠 (且通常確實) 允許隨機存取,或存取檔案開頭而不檢查檔案結尾。這是刻意設計,因為可以從 \(C_i\)解密 \(M_i\) ,而不會解密所有先前和剩餘的密文區塊。
不過,請務必小心,不要讓使用者混淆檔案結尾和解密錯誤:不論是哪一種情況,API 都可能需要傳回錯誤,而忽略差異將導致對手能夠有效截斷檔案。
金鑰的序列化與剖析
為了將金鑰序列化為「Tink Proto」格式,我們會先以明顯的方式將參數對應至透過 aes_gcm_hkdf_streaming.proto 提供的 proto,version
欄位必須設為 0。然後,我們會使用一般的 Proto 序列化進行序列化,然後將產生的字串嵌入 KeyData 原型的欄位值中。我們將 type_url
欄位設為 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
。接著,我們將 key_material_type
設為 SYMMETRIC
,並將其嵌入金鑰組。我們通常會將 output_prefix_type
設為 RAW
。例外狀況是,如果剖析金鑰時使用的 output_prefix_type
值不同,Tink 可能會寫入 RAW
或先前的值。
為剖析金鑰,我們會反向執行上述程序 (剖析原型時如往常)。系統會忽略欄位 key_material_type
。您可以忽略 output_prefix_type
的值,或是拒絕 output_prefix_type
與 RAW
不同的鍵。version
與 0 不同的金鑰必須遭到拒絕。
已知問題
實作上述加密函式不應成為安全的分支,請參閱分支安全相關說明。
參考資料
除非另有註明,否則本頁面中的內容是採用創用 CC 姓名標示 4.0 授權,程式碼範例則為阿帕契 2.0 授權。詳情請參閱《Google Developers 網站政策》。Java 是 Oracle 和/或其關聯企業的註冊商標。
上次更新時間:2025-07-25 (世界標準時間)。
[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-07-25 (世界標準時間)。"],[[["\u003cp\u003eThis document specifies the \u003ccode\u003eAesGcmHkdfStreamingKey\u003c/code\u003e encryption function, based on HRRV15 and analyzed in HS20, for secure streaming data encryption.\u003c/p\u003e\n"],["\u003cp\u003eKeys consist of \u003ccode\u003eKeyValue\u003c/code\u003e, \u003ccode\u003eCiphertextSegmentSize\u003c/code\u003e, \u003ccode\u003eDerivedKeySize\u003c/code\u003e, and \u003ccode\u003eHkdfHashType\u003c/code\u003e, with specific size and validity constraints.\u003c/p\u003e\n"],["\u003cp\u003eEncryption involves creating a header, splitting the message into segments, encrypting each segment using AES-GCM with a derived key, and concatenating the results.\u003c/p\u003e\n"],["\u003cp\u003eDecryption reverses this process, allowing random access to segments for efficient retrieval of specific message parts.\u003c/p\u003e\n"],["\u003cp\u003eSerialization and parsing of keys utilize Tink Proto format with specific requirements for \u003ccode\u003eversion\u003c/code\u003e, \u003ccode\u003etype_url\u003c/code\u003e, and \u003ccode\u003ekey_material_type\u003c/code\u003e fields.\u003c/p\u003e\n"]]],["The document defines AES-GCM-HKDF Streaming key encryption. Encryption involves creating a header with a random salt and nonce prefix, deriving a key using HKDF with associated data, and splitting the message into segments. Each segment is encrypted using AES-GCM with a unique initialization vector. The encrypted segments are concatenated with the header to form the final ciphertext. Decryption reverses this process. Key serialization follows the Tink Proto format, with specific field settings. Keys can be parsed in the same way.\n"],null,["# AES-GCM-HKDF Streaming AEAD\n\nThis document formally defines the mathematical function represented by\nAES-GCM-HKDF Streaming keys, encoded in proto format as\n`type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey`.\n\nThis encryption is loosely based on HRRV15^[1](#fn1)^. For security analysis, we refer\nto HS20^[2](#fn2)^.\n\nKey and parameters\n------------------\n\nKeys are described by the following parts (all sizes in this document are in\nbytes):\n\n- \\\\(\\\\mathrm{KeyValue}\\\\), a byte string.\n- \\\\(\\\\mathrm{CiphertextSegmentSize} \\\\in \\\\{1, 2, \\\\ldots, 2\\^{31}-1\\\\}\\\\).\n- \\\\(\\\\mathrm{DerivedKeySize} \\\\in \\\\{16, 32\\\\}\\\\).\n- \\\\(\\\\mathrm{HkdfHashType} \\\\in \\\\{\\\\mathrm{SHA1}, \\\\mathrm{SHA256}, \\\\mathrm{SHA512}\\\\}\\\\).\n\nValid keys additionally satisfy the following properties:\n\n- \\\\(\\\\mathrm{len}(\\\\mathrm{KeyValue}) \\\\geq \\\\mathrm{DerivedKeySize}\\\\).\n- \\\\(\\\\mathrm{CiphertextSegmentSize} \\\u003e \\\\mathrm{DerivedKeySize} + 24\\\\) (This equals \\\\(\\\\mathrm{len}(\\\\mathrm{Header}) + 16\\\\) as explained later).\n\nKeys that violate any of these properties are rejected by Tink, either\nwhen the key is parsed or when the corresponding primitive is created.\n\nEncryption function\n-------------------\n\nTo encrypt a message \\\\(\\\\mathrm{Msg}\\\\) with associated data\n\\\\(\\\\mathrm{AssociatedData}\\\\), we create a header, split the message into\nsegments, encrypt each segment, and concatenate the encrypted segments.\n\n### Create the header\n\nWe pick a uniform random string \\\\(\\\\mathrm{Salt}\\\\) of length\n\\\\(\\\\mathrm{DerivedKeySize}\\\\) and a uniform random string \\\\(\\\\mathrm{NoncePrefix}\\\\)\nof length 7.\n\nWe then set \\\\(\\\\mathrm{Header} := \\\\mathrm{len}(\\\\mathrm{Header}) \\\\\\| \\\\mathrm{Salt}\n\\\\\\| \\\\mathrm{NoncePrefix}\\\\), where the length of the header is encoded as a single\nbyte. Note that \\\\(\\\\mathrm{len}(\\\\mathrm{Header}) \\\\in \\\\{24, 40\\\\}\\\\).\n\nNext, we use HKDF^[3](#fn3)^ with the hash function given by \\\\(\\\\mathrm{HkdfHashType}\\\\)\nand inputs \\\\(\\\\mathrm{ikm} := \\\\mathrm{KeyValue}\\\\), \\\\(\\\\mathrm{salt} :=\n\\\\mathrm{Salt}\\\\), and \\\\(\\\\mathrm{info} := \\\\mathrm{AssociatedData}\\\\), with output\nlength \\\\(\\\\mathrm{DerivedKeySize}\\\\). We call the result \\\\(\\\\mathrm{DerivedKey}\\\\).\n\n### Split the message\n\nThe message \\\\(\\\\mathrm{Msg}\\\\) is next split into parts: \\\\(\\\\mathrm{Msg} = M_0 \\\\\\|\nM_1 \\\\\\| \\\\cdots \\\\\\| M_{n-1}\\\\).\n\nTheir lengths are chosen to satisfy:\n\n- \\\\(\\\\mathrm{len}(M_0) \\\\in \\\\{0,\\\\ldots, \\\\mathrm{CiphertextSegmentSize} - \\\\mathrm{len}(\\\\mathrm{Header}) - \\\\mathrm{16}\\\\}\\\\).\n- If \\\\(n\\\u003e1\\\\), then \\\\(\\\\mathrm{len}(M_1), \\\\ldots, \\\\mathrm{len}(M_{n-1}) \\\\in \\\\{1,\\\\ldots, \\\\mathrm{CiphertextSegmentSize} - \\\\mathrm{16}\\\\}\\\\).\n- If \\\\(n\\\u003e1\\\\), then \\\\(\\\\mathrm{len}(M_{0}), \\\\ldots, \\\\mathrm{len}(M_{n-2})\\\\) must have maximal length according to the above to constraints.\n\n\\\\(n\\\\) may be at most \\\\(2\\^{32}\\\\). Otherwise, encryption fails.\n\n### Encrypt the blocks\n\nTo encrypt segment \\\\(M_i\\\\), we compute \\\\(\\\\mathrm{IV}_i := \\\\mathrm{NoncePrefix}\n\\\\\\| \\\\mathrm{i} \\\\\\| b\\\\), where \\\\(\\\\mathrm{i}\\\\) is 4 bytes in big-endian encoding and\nbyte $b$ is `0x00` if $i \\\u003c n-1$ and `0x01` otherwise.\n\nWe then encrypt \\\\(M_i\\\\) using AES-GCM^[4](#fn4)^, where the key is\n\\\\(\\\\mathrm{DerivedKey}\\\\), the initialization vector is \\\\(\\\\mathrm{IV}_i\\\\), and the\nassociated data is the empty string. \\\\(C_i\\\\) is the result of this encryption\n(i.e. the concatenation of \\\\(C\\\\) and \\\\(T\\\\) in section 5.2.1.2 of the linked\nAES-GCM reference).\n\n### Concatenate the encrypted segments\n\nFinally, all segments are concatenated as \\\\(\\\\mathrm{Header} \\\\\\| C_0 \\\\\\| \\\\cdots \\\\\\|\nC_{n-1}\\\\), which is the final ciphertext.\n\nDecryption\n----------\n\nDecryption inverts the encryption. We use the header to obtain\n\\\\(\\\\mathrm{NoncePrefix}\\\\), and decrypt each segment of ciphertext individually.\n\nAPIs may (and typically do) allow random access, or access to the beginning of a\nfile without inspecting the end of the file. This is intentional, since it is\npossible to decrypt \\\\(M_i\\\\) from \\\\(C_i\\\\), without decrypting all previous and\nremaining ciphertext blocks.\n\nHowever, APIs should be careful to not allow users to confuse end-of-file and\ndecryption errors: in both cases the API probably has to return an error, and\nignoring the difference can lead to an adversary being able to effectively\ntruncate files.\n\nSerialization and parsing of keys\n---------------------------------\n\nTo serialize a key in the \"Tink Proto\" format, we first map the parameters in\nthe obvious way into the proto given at\n[aes_gcm_hkdf_streaming.proto](https://github.com/tink-crypto/tink-java/blob/main/proto/aes_gcm_hkdf_streaming.proto). The field `version` needs to\nbe set to 0. We then serialize this using normal proto serialization, and embed\nthe resulting string in the value of field of a [KeyData](https://github.com/tink-crypto/tink-java/blob/main/proto/tink.proto) proto. We\nset the `type_url` field to\n`type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey`. We then set\n`key_material_type` to `SYMMETRIC`, and embed this into a keyset. We usually set\nthe `output_prefix_type` to `RAW`. The exception is that if the key was parsed\nwith a different value set for `output_prefix_type`, Tink may either write `RAW`\nor the previous value.\n\nTo parse a key, we reverse the above process (in the usual way when parsing\nprotos). The field `key_material_type` is ignored. The value of\n`output_prefix_type` can either be ignored, or keys which have\n`output_prefix_type` different from `RAW` can be rejected. Keys which have a\n`version` different from 0 must be rejected.\n\nKnown issues\n------------\n\nImplementations of the above encryption function are not expected to be fork\nsafe. See [Fork Safety](/tink/issues/fork-safety).\n\nReferences\n----------\n\n*** ** * ** ***\n\n1. Hoang, Reyhanitabar, Rogaway, Vizar, 2015. Online authenticated-encryption\n and its nonce-reuse misuse-resistance. CRYPTO 2015.\n \u003chttps://eprint.iacr.org/2015/189\u003e [↩](#fnref1)\n\n2. Hoang, Shen, 2020. Security of Streaming Encryption in Google's\n Tink Library. \u003chttps://eprint.iacr.org/2020/1019\u003e [↩](#fnref2)\n\n3. RFC 5869. HMAC-based Extract-and-Expand Key Derivation Function (HKDF).\n \u003chttps://www.rfc-editor.org/rfc/rfc5869\u003e [↩](#fnref3)\n\n4. NIST SP 800-38D. Recommendation for Block Cipher Modes of Operation:\n Galois/Counter Mode (GCM) and GMAC. \u003chttps://csrc.nist.gov/pubs/sp/800/38/d/final\u003e [↩](#fnref4)"]]