ASP.NET Core 中的上下文标头

背景和理论

在数据保护系统中,“密钥”表示可以提供经过身份验证的加密服务的对象。 每个密钥都由唯一的 ID(GUID)标识,并携带算法信息和熵材料。 它旨在使每个密钥具有唯一的萎缩性,但系统无法强制实施,我们还需要为可能通过修改密钥圈中现有密钥的算法信息来手动更改密钥圈的开发人员负责。 为了达到我们的安全要求,鉴于这些情况,数据保护系统具有 加密敏捷性的概念,它允许跨多个加密算法安全地使用单个萎缩值。

大多数支持加密敏捷性的系统通过在有效负载中包含有关算法的一些标识信息来实现这一点。 算法的 OID 通常是一个很好的候选项。 但是,我们遇到了一个问题是,有多种方法可以指定相同的算法:“AES”(CNG)和托管的 Aes、AesManaged、AesCryptoServiceProvider、AesCng 和 RijndaelManaged(给定特定参数)类实际上都是相同的,我们需要维护所有这些算法与正确的 OID 的映射。 如果开发人员想要提供自定义算法(甚至是 AES 的另一个实现),他们必须告诉我们其 OID。 此额外的注册步骤使系统配置特别痛苦。

退后一步,我们决定从错误的方向接近问题。 OID 会告诉你算法是什么,但我们实际上并不关心这一点。 如果需要在两种不同的算法中安全地使用单个萎缩值,则我们不必知道算法的实际是什么。 我们实际上关心的是他们的行为。 任何合适的对称块密码算法也是一个强伪随机排列(PRP):固定输入(密钥、链接模式、IV、明文),密码文本输出将以压倒性概率与给定相同输入的任何其他对称块密码算法不同。 同样,任何合适的键控哈希函数也是一个强大的伪随机函数(PRF),并且固定输入集下,其输出在很大程度上将与任何其他键控哈希函数不同。

我们使用强 PRP 和 PRF 的概念来构建一个上下文标头。 此上下文标头实质上用作任何给定操作中使用算法的稳定指纹,并提供数据保护系统所需的加密敏捷性。 此标头可重现,稍后将用作 子项派生过程的一部分。 根据基础算法的作模式,有两种不同的方法来生成上下文标头。

CBC 模式加密 + HMAC 身份验证

上下文标头包含以下组件:

  • [16 位]值 00 00,表示“CBC 加密 + HMAC 身份验证”的标记。

  • [32 位] 对称块密码算法密钥长度(以字节为单位,大端序)。

  • [32 位] 对称块密码算法的块大小(以字节为单位,采用大端序)。

  • [32 位]HMAC 算法的密钥长度(以字节为单位,big-endian)。 (当前密钥大小始终与摘要大小匹配。

  • [32 位]HMAC 算法的摘要大小(以字节为单位,big-endian)。

  • EncCBC(K_E, IV, ""),这是给定空字符串输入的对称块密码算法的输出,其中 IV 是全零向量。 下面描述了 K_E 的构造。

  • MAC(K_H, ""),这是给定空字符串输入的 HMAC 算法的输出。 以下描述了 K_H 的构造。

理想情况下,我们可以为K_EK_H传递全零向量。 但是,我们希望避免在执行任何作(尤其是 DES 和 3DES)之前基础算法检查是否存在弱键的情况,这阻止使用简单或可重复的模式(如全零向量)。

相反,我们使用 NIST SP800-108 KDF 的计数器模式 (请参阅 NIST SP800-108,第5.1节),其中键、标签和上下文均为零长度,HMACSHA512 作为基础 PRF。 我们计算得到 | K_E | + | K_H | 字节的输出,然后将结果分解为 K_EK_H。 从数学上看,如下所示。

( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

示例:AES-192-CBC + HMACSHA256

例如,假设对称块密码算法为 AES-192-CBC,并且验证算法HMACSHA256。 系统将使用以下步骤生成上下文标头。

首先,设 ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""),其中 | K_E | = 192 bits| K_H | = 256 bits 根据指定的算法。 这会在以下示例中导致 K_E = 5BB6..21DDK_H = A04A..00A9

5B B6 C9 83 13 78 22 1D 8E 10 73 CA CF 65 8E B0
61 62 42 71 CB 83 21 DD A0 4A 05 00 5B AB C0 A2
49 6F A5 61 E3 E2 49 87 AA 63 55 CD 74 0A DA C4
B7 92 3D BF 59 90 00 A9

接下来,计算 Enc_CBC (K_E, IV, ""),作为对给定的 AES-192-CBC 和所述的 IV = 0*K_E 的结果。

result := F474B1872B3B53E4721DE19C0841DB6F

接下来,计算 MAC(K_H, "") 上面给出 K_H 的HMACSHA256。

result := D4791184B996092EE1202F36E8608FA8FBD98ABDFF5402F264B1D7211536220C

这会生成以下完整上下文标头:

00 00 00 00 00 18 00 00 00 10 00 00 00 20 00 00
00 20 F4 74 B1 87 2B 3B 53 E4 72 1D E1 9C 08 41
DB 6F D4 79 11 84 B9 96 09 2E E1 20 2F 36 E8 60
8F A8 FB D9 8A BD FF 54 02 F2 64 B1 D7 21 15 36
22 0C

此上下文标识头是经过身份验证的加密算法对的摘要(AES-192-CBC 加密 + HMACSHA256 验证)。 如上所述,组件包括:

  • 标记 (00 00)

  • 分组密码密钥长度 (00 00 00 18)

  • 分组密码的块大小 (00 00 00 10)

  • HMAC 密钥长度 (00 00 00 20)

  • HMAC 摘要大小 (00 00 00 20)

  • 块密码 PRP 输出 (F4 74 - DB 6F)

  • HMAC PRF 输出 (D4 79 - end)

注释

无论算法实现是由 Windows CNG 提供,还是由托管的 SymmetricAlgorithm 和 KeyedHashAlgorithm 类型提供,CBC 模式加密和 HMAC 身份验证上下文标头的构建方式都是相同的。 这允许在不同作系统上运行的应用程序可靠地生成相同的上下文标头,即使算法的实现在 OS 之间有所不同。 在实践中,KeyedHashAlgorithm 不一定要是标准的 HMAC。它可以是任何一种带密钥的哈希算法类型。

示例:3DES-192-CBC + HMACSHA1

首先,设定 ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""),其中 | K_E | = 192 bits| K_H | = 160 bits 根据指定的算法。 这会在以下示例中导致 K_E = A219..E2BBK_H = DC4A..B464

A2 19 60 2F 83 A9 13 EA B0 61 3A 39 B8 A6 7E 22
61 D9 F8 6C 10 51 E2 BB DC 4A 00 D7 03 A2 48 3E
D1 F7 5A 34 EB 28 3E D7 D4 67 B4 64

接下来,计算 Enc_CBC (K_E, IV, ""),对于如上所述的给定 3DES-192-CBC 和参数IV = 0*K_E

result := ABB100F81E53E10E

接下来,为上面给出的 K_H 计算HMACSHA1,结果为 MAC(K_H, "")

result := 76EB189B35CF03461DDF877CD9F4B1B4D63A7555

这会生成完整上下文头,该标头是经过身份验证的加密算法对(3DES-192-CBC 加密 + HMACSHA1 验证)的特征标记,如下所示:

00 00 00 00 00 18 00 00 00 08 00 00 00 14 00 00
00 14 AB B1 00 F8 1E 53 E1 0E 76 EB 18 9B 35 CF
03 46 1D DF 87 7C D9 F4 B1 B4 D6 3A 75 55

组件按如下所示分解:

  • 标记 (00 00)

  • 块密码密钥长度 (00 00 00 18)

  • 分组密码分组大小 (00 00 00 08)

  • HMAC 密钥长度 (00 00 00 14)

  • HMAC 摘要大小 (00 00 00 14)

  • 块密码 PRP 输出 (AB B1 - E1 0E)

  • HMAC PRF 输出 (76 EB - end)

Galois/计数器模式加密 + 身份验证

上下文标头包含以下组件:

  • [16 位]值 00 01,表示“GCM 加密 + 身份验证”的标记。

  • [32 位]对称块密码算法的密钥长度(以字节为单位,大端序)。

  • [32 位]在经过身份验证的加密操作期间使用的 nonce 大小(以字节为单位,big-endian)。 (对于我们的系统,nonce 大小固定为96位。)

  • [32 位]对称块密码算法的块大小(以字节为单位,大端)。 (对于 GCM,这是固定的,块大小为 128 位。)

  • [32 位数字] 认证加密函数生成的认证标签大小(以字节为单位,big-endian)。 对于我们的系统,这是固定在标签大小 = 128 位。

  • [128 位] Enc_GCM (K_E, nonce, "")标记,它是给定空字符串输入的对称块密码算法的输出,其中 nonce 是 96 位全零向量。

K_E 使用与 CBC 加密 + HMAC 身份验证方案中相同的机制派生。 但是,由于这里没有 K_H 参与,我们基本上有 | K_H | = 0,算法简化为以下形式。

K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

示例:AES-256-GCM

首先,让K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""),其中| K_E | = 256 bits

K_E := 22BC6F1B171C08C4AE2F27444AF8FC8B3087A90006CAEA91FDCFB47C1B8733B8

接下来,计算 AES-256-GCM 的 Enc_GCM (K_E, nonce, "") 的身份验证标记,给定 nonce = 096K_E 如上所述。

result := E7DCCE66DF855A323A6BB7BD7A59BE45

这会生成以下完整上下文标头:

00 01 00 00 00 20 00 00 00 0C 00 00 00 10 00 00
00 10 E7 DC CE 66 DF 85 5A 32 3A 6B B7 BD 7A 59
BE 45

组件按如下所示分解:

  • 标记 (00 01)

  • 块密码密钥长度 (00 00 00 20)

  • nonce 大小 (00 00 00 0C)

  • 块加密算法块大小 (00 00 00 10)

  • 身份验证标记大小 (00 00 00 10)

  • 块密码 (E7 DC - end) 运行后生成的身份验证标记。