AES-CTR HMAC 流式传输 AEAD
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
本文档正式定义了由 AES-CTR HMAC 流式密钥(以 proto 格式编码为 type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
)表示的数学函数。
此加密方式大致基于 [HRRV15]1。如需分析安全性,请参阅 [HS20]2。另请注意,Tink 跨语言测试包含一个测试 aes_ctr_hmac_streaming_key_test.py,其中包含 test_manually_created_test_vector
,其中包含有关如何获取密文的完整演示。
键和参数
键由以下部分描述(本文档中的所有大小均以字节为单位):
- \(\mathrm{InitialKeyMaterial}\),字节字符串:初始密钥材料。
- \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\)。
- \(\mathrm{DerivedKeySize} \in \{16, 32\}\)。
- \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256},
\mathrm{SHA512}\}\)。
- \(\mathrm{HmacHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256},
\mathrm{SHA512}\}\)。
- \(\mathrm{HmacTagSize} \in \mathbb{N}\)。
有效键还满足以下属性:
- \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\)。
- 如果 \(\mathrm{HmacHashType} = \mathrm{SHA1}\) ,则 \(\mathrm{HmacTagSize}
\in \{10, \ldots, 20\}\)。
- 如果 \(\mathrm{HmacHashType} = \mathrm{SHA256}\) ,则 \(\mathrm{HmacTagSize}
\in \{10, \ldots, 32\}\)。
- 如果 \(\mathrm{HmacHashType} = \mathrm{SHA512}\) ,则 \(\mathrm{HmacTagSize}
\in \{10, \ldots, 64\}\)。
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} +
\mathrm{HmacTagSize} + 8\) (如后所述,此值等于\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) )。
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{DerivedKeySize} + 32\) 的密钥材料:\(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt},
\mathrm{AssociatedData})\)。这些输入会用于\(\mathrm{HKDF}\)的相应输入: \(\mathrm{InitialKeyMaterial}\) 是 \(\mathrm{ikm}\)、\(\mathrm{Salt}\) 是盐,\(\mathrm{AssociatedData}\) 用作 \(\mathrm{info}\)。
然后,字符串 \(k\) 会拆分为两个部分 \(k_1 \| k_2 := k\),即\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) 和 \(\mathrm{len}(k_2) = 32\)。
拆分消息
接下来,消息 \(M\) 会拆分为以下部分: \(M = M_0 \| M_1 \| \cdots
\| M_{n-1}\)。
其长度应满足以下条件:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} -
\mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\)。
- 如果 \(n > 1\),则 \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1})
\in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\)。
- 如果为 \(n > 1\),则 \(M_{0}, \ldots, M_{n-2}\) 必须根据上述约束条件具有最大长度。
在此拆分中, \(n\) 不得超过 \(2^{32}\)。否则,加密将失败。
加密分块
如需对分段 \(M_i\)进行加密,我们首先计算\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\),其中我们使用大端字节编码将 \(\mathrm{i}\) 编码为 4 个字节,并将字节 $b$ 设置为 $i < n-1$ 时为 0x00
,否则为 0x01
。
然后,我们 \(M_i\) 使用 AES CTR 密钥 \(k_1\)和初始化矢量\(\mathrm{IV}_i\)进行加密。换句话说,AES 调用的输入为\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\),其中 \(\mathrm{IV}_i\) 被解释为大端整数。这会产生 \(C'_i\)。
我们使用 HMAC 和 \(\mathrm{HmacHashType}\) 提供的哈希函数以及对串联\(\mathrm{IV}_i \| C'_i\)的密钥 \(k_2\) 计算标记。
然后,我们将密文与标记串联起来,以获取 \(C_i\)。
串联这些细分
最后,将所有分段串联为\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\),即最终的密文。
解密函数
解密只是对加密进行反转。我们使用标头获取 Nonce,并单独解密每个密文段。
API 可能会(通常也确实会)允许随机访问,或者允许访问文件开头,而无需检查文件结尾。这是有意为之,因为可以从 \(C_i\)解密 \(M_i\) ,而无需解密所有之前和剩余的密文块。
不过,API 应注意不要让用户混淆文件结束错误和解密错误:在上述两种情况下,API 都可能必须返回错误,而忽略这种差异可能会导致攻击者能够有效地截断文件。
键的序列化和解析
如需以“Tink Proto”格式序列化密钥,我们首先以显而易见的方式将参数映射到 aes_ctr_hmac_streaming.proto 中给出的 proto。version
字段需要设置为 0。然后,我们使用常规 Proto 序列化功能对其进行序列化,并将生成的字符串嵌入 KeyData Proto 字段的值中。我们将 type_url
字段设置为 type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
。然后,我们将 key_material_type
设置为 SYMMETRIC
,并将其嵌入到密钥集中。我们通常将 output_prefix_type
设置为 RAW
。唯一的例外情况是,如果为 output_prefix_type
设置了其他值来解析键,Tink 可能会写入 RAW
或之前的值。
如需解析键,我们会反向执行上述过程(以解析 proto 的常规方式)。系统会忽略 key_material_type
字段。您可以忽略 output_prefix_type
的值,也可以拒绝 output_prefix_type
与 RAW
不同的键。系统会拒绝 version
不同于 0 的键。
参考
如未另行说明,那么本页面中的内容已根据知识共享署名 4.0 许可获得了许可,并且代码示例已根据 Apache 2.0 许可获得了许可。有关详情,请参阅 Google 开发者网站政策。Java 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-07-25。
[null,null,["最后更新时间 (UTC):2025-07-25。"],[[["\u003cp\u003eThis document defines the AES-CTR HMAC Streaming key and its mathematical function for encryption and decryption, loosely based on the HRRV15 and HS20 security analyses.\u003c/p\u003e\n"],["\u003cp\u003eThe encryption process involves creating a header, splitting the message into segments, encrypting each segment using AES CTR with a derived key, and concatenating the segments along with the header.\u003c/p\u003e\n"],["\u003cp\u003eKeys are described by parameters including InitialKeyMaterial, CiphertextSegmentSize, DerivedKeySize, HkdfHashType, HmacHashType, and HmacTagSize, with specific validity constraints.\u003c/p\u003e\n"],["\u003cp\u003eDecryption reverses the encryption process, allowing for potential random access to segments but requiring careful handling of end-of-file and decryption errors.\u003c/p\u003e\n"],["\u003cp\u003eSerialization and parsing of keys involve mapping parameters to a proto format, embedding them in a KeyData proto, and setting specific fields like type_url, key_material_type, and output_prefix_type.\u003c/p\u003e\n"]]],["AES-CTR HMAC Streaming keys define an encryption method involving key parameters like `InitialKeyMaterial`, `CiphertextSegmentSize`, `DerivedKeySize`, `HkdfHashType`, `HmacHashType`, and `HmacTagSize`. Encryption creates a header with a random salt and nonce prefix, derives key material using HKDF, and splits the message into segments. Each segment is encrypted using AES-CTR with a unique IV and HMAC tag, then concatenated to form the ciphertext. Decryption reverses this process. Keys are serialized into a specific proto format.\n"],null,["# AES-CTR HMAC Streaming AEAD\n\nThis document formally defines the mathematical function represented by\nAES-CTR HMAC Streaming keys (encoded in proto format as\n`type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey`).\n\nThis encryption is loosely based on \\[HRRV15\\]^[1](#fn1)^. For an analysis of the\nsecurity we refer to \\[HS20\\]^[2](#fn2)^. Note also that the Tink cross language tests\nhave a test\n[aes_ctr_hmac_streaming_key_test.py](https://github.com/google/tink/blob/master/testing/cross_language/streaming_aead/aes_ctr_hmac_streaming_key_test.py) which\ncontains `test_manually_created_test_vector` with a complete walkthrough on how\nto get a ciphertext.\n\nKey and parameters\n------------------\n\nKeys are described by the following parts (all sizes in this document are in\nbytes):\n\n- \\\\(\\\\mathrm{InitialKeyMaterial}\\\\), a byte string: the initial key material.\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- \\\\(\\\\mathrm{HmacHashType} \\\\in \\\\{\\\\mathrm{SHA1}, \\\\mathrm{SHA256}, \\\\mathrm{SHA512}\\\\}\\\\).\n- \\\\(\\\\mathrm{HmacTagSize} \\\\in \\\\mathbb{N}\\\\).\n\nValid keys additionally satisfy the following properties:\n\n- \\\\(\\\\mathrm{len}(\\\\mathrm{InitialKeyMaterial}) \\\\geq \\\\mathrm{DerivedKeySize}\\\\).\n- If \\\\(\\\\mathrm{HmacHashType} = \\\\mathrm{SHA1}\\\\) then \\\\(\\\\mathrm{HmacTagSize} \\\\in \\\\{10, \\\\ldots, 20\\\\}\\\\).\n- If \\\\(\\\\mathrm{HmacHashType} = \\\\mathrm{SHA256}\\\\) then \\\\(\\\\mathrm{HmacTagSize} \\\\in \\\\{10, \\\\ldots, 32\\\\}\\\\).\n- If \\\\(\\\\mathrm{HmacHashType} = \\\\mathrm{SHA512}\\\\) then \\\\(\\\\mathrm{HmacTagSize} \\\\in \\\\{10, \\\\ldots, 64\\\\}\\\\).\n- \\\\(\\\\mathrm{CiphertextSegmentSize} \\\u003e \\\\mathrm{DerivedKeySize} + \\\\mathrm{HmacTagSize} + 8\\\\) (This equals \\\\(\\\\mathrm{len}(\\\\mathrm{Header}) + \\\\mathrm{HmacTagSize}\\\\) as explained later).\n\nKeys which do not satisfy 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,\nsplit the message into segments, encrypt each segment, and concatenate\nthe segments. We explain these steps in the following.\n\n### Creating the header\n\nTo create the header, we first pick a uniform random string \\\\(\\\\mathrm{Salt}\\\\)\nof length \\\\(\\\\mathrm{DerivedKeySize}\\\\). We next pick a uniform random string\n\\\\(\\\\mathrm{NoncePrefix}\\\\) of length 7.\n\nWe then set\n\\\\(\\\\mathrm{Header} := \\\\mathrm{len}(\\\\mathrm{Header}) \\\\\\| \\\\mathrm{Salt} \\\\\\|\n\\\\mathrm{NoncePrefix}\\\\),\nwhere the length of the header is encoded as a single byte. We note that\n\\\\(\\\\mathrm{len}(\\\\mathrm{Header}) \\\\in \\\\{24, 40\\\\}\\\\).\n\nNext, we use HKDF^[3](#fn3)^ with hash-function \\\\(\\\\mathrm{HkdfHashType}\\\\)\nto compute key material of length\n\\\\(\\\\mathrm{DerivedKeySize} + 32\\\\) for this message:\n\\\\(k := \\\\mathrm{HKDF}(\\\\mathrm{InitialKeyMaterial}, \\\\mathrm{Salt},\n\\\\mathrm{AssociatedData})\\\\).\nThe inputs are used in the corresponding respective inputs of\n\\\\(\\\\mathrm{HKDF}\\\\): \\\\(\\\\mathrm{InitialKeyMaterial}\\\\) is \\\\(\\\\mathrm{ikm}\\\\),\n\\\\(\\\\mathrm{Salt}\\\\) is the salt, and\n\\\\(\\\\mathrm{AssociatedData}\\\\) is used as \\\\(\\\\mathrm{info}\\\\).\n\nThe string \\\\(k\\\\) is then split into two parts \\\\(k_1 \\\\\\| k_2 := k\\\\),\nsuch that\n\\\\(\\\\mathrm{len}(k_1) = \\\\mathrm{DerivedKeySize}\\\\) and \\\\(\\\\mathrm{len}(k_2) = 32\\\\).\n\n### Splitting the message\n\nThe message \\\\(M\\\\) is next split into parts: \\\\(M = M_0 \\\\\\| M_1 \\\\\\| \\\\cdots\n\\\\\\| M_{n-1}\\\\).\n\nTheir lengths are chosen so that they satisfy:\n\n- \\\\(\\\\mathrm{len}(M_0) \\\\in \\\\{0,\\\\ldots, \\\\mathrm{CiphertextSegmentSize} - \\\\mathrm{len}(\\\\mathrm{Header}) - \\\\mathrm{HmacTagSize}\\\\}\\\\).\n- If \\\\(n \\\u003e 1\\\\), then \\\\(\\\\mathrm{len}(M_1), \\\\ldots, \\\\mathrm{len}(M_{n-1}) \\\\in \\\\{1,\\\\ldots, \\\\mathrm{CiphertextSegmentSize} - \\\\mathrm{HmacTagSize}\\\\}\\\\).\n- If \\\\(n \\\u003e 1\\\\), then \\\\(M_{0}, \\\\ldots, M_{n-2}\\\\) must have maximal length according to the above to constraints.\n\nIn this splitting, \\\\(n\\\\) may at most be \\\\(2\\^{32}\\\\). Otherwise, encryption fails.\n\n### Encrypting the blocks\n\nTo encrypt segment \\\\(M_i\\\\), we first compute\n\\\\(\\\\mathrm{IV}_i := \\\\mathrm{NoncePrefix} \\\\\\| \\\\mathrm{i} \\\\\\| b \\\\\\| 0x00000000\\\\),\nwhere we encode \\\\(\\\\mathrm{i}\\\\) in 4\nbytes using big-endian encoding, and set the byte $b$ to be `0x00` if $i \\\u003c n-1$\nand `0x01` otherwise.\n\nWe then encrypt \\\\(M_i\\\\) using AES CTR key \\\\(k_1\\\\), and initialization vector\n\\\\(\\\\mathrm{IV}_i\\\\). In other words, the inputs to the invocations of AES are\n\\\\(\\\\mathrm{IV}_i, \\\\mathrm{IV}_i + 1, \\\\mathrm{IV}_i + 2, \\\\ldots\\\\)\nwhere \\\\(\\\\mathrm{IV}_i\\\\) is interpreted as big-endian integer.\nThis yields \\\\(C'_i\\\\).\n\nWe compute the tag using HMAC with the hash function given\nby \\\\(\\\\mathrm{HmacHashType}\\\\) and with key \\\\(k_2\\\\) over the concatenation\n\\\\(\\\\mathrm{IV}_i \\\\\\| C'_i\\\\).\n\nWe then concatenate the ciphertext followed by the tag to get \\\\(C_i\\\\).\n\n### Concatenate the segments\n\nFinally, all segments are concatenated as\n\\\\(\\\\mathrm{Header} \\\\\\| C_0 \\\\\\| \\\\cdots \\\\\\| C_{n-1}\\\\), which is the final ciphertext.\n\nDecryption function\n-------------------\n\nDecryption simply inverts the encryption. We use the header to obtain the nonce,\nand decrypt each segment of ciphertext individually.\n\nAPIs may (and typically do) allow random access, or access to the\nbeginning of a file without inspecting the end of the file. This is\nintentional, since it is possible to decrypt \\\\(M_i\\\\) from \\\\(C_i\\\\),\nwithout decrypting all previous and remaining 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\nin the obvious way into the proto given at\n[aes_ctr_hmac_streaming.proto](https://github.com/tink-crypto/tink-java/blob/main/proto/aes_ctr_hmac_streaming.proto).\nThe field `version` needs to be set to 0.\nWe then serialize this using normal proto serialization, and embed the resulting\nstring in the value of field of a\n[KeyData](https://github.com/tink-crypto/tink-java/blob/main/proto/tink.proto) proto. We set the `type_url` field\nto `type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey`.\nWe then set `key_material_type` to `SYMMETRIC`, and embed this into a keyset. We\nusually set the `output_prefix_type` to `RAW`. The exception is that if the key\nwas parsed with a different value set for `output_prefix_type`,\nTink may either write `RAW` or 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\nhave `output_prefix_type` different from `RAW` can be rejected.\nKeys which have a `version` different from 0 are be rejected.\n\nReferences\n----------\n\n*** ** * ** ***\n\n1. \\[HRRV15\\] Hoang, Reyhanitabar, Rogaway, Vizar. Online\n authenticated-encryption and its nonce-reuse misuse-resistance. CRYPTO 2015.\n \u003chttps://eprint.iacr.org/2015/189\u003e [↩](#fnref1)\n\n2. \\[HS20\\] Security of Streaming Encryption in Google's Tink Library.\n Hoang, Shen, 2020.\n \u003chttps://eprint.iacr.org/2020/1019\u003e [↩](#fnref2)\n\n3. \\[HKDF\\] HMAC-based Extract-and-Expand Key Derivation Function (HKDF),\n RFC 5869. \u003chttps://www.rfc-editor.org/rfc/rfc5869\u003e [↩](#fnref3)"]]