如何为 AES/CTR/NoPadding 选择合适的 IV(初始化向量)?

Posted

技术标签:

【中文标题】如何为 AES/CTR/NoPadding 选择合适的 IV(初始化向量)?【英文标题】:How to pick an appropriate IV (Initialization Vector) for AES/CTR/NoPadding? 【发布时间】:2011-06-04 06:08:22 【问题描述】:

我想加密由 web 应用程序编写的 cookie,并且我希望将 cookie 的大小保持在最小,因此我选择了 AES/CTR/NoPadding。

您会建议将什么用作足够随机且仍保持应用无状态的 IV。我知道我可以生成一个随机 IV 并将其附加到消息中,但这会增加 cookie 的大小。

此外,对于 128 位 AES,建议的 IV 大小是多少?

其他人是怎么做的?是否存在任何“久经考验”的方法?我不想重新发明***。

【问题讨论】:

为什么要关心 cookie 的大小是否增大了几个字节? 【参考方案1】:

您会建议将什么用作足够随机且仍保持应用无状态的 IV。我知道我可以生成一个随机 IV 并将其附加到消息中,但这会增加 cookie 的大小。

这听起来更棘手,因为您真的不想重复随机数(IV 的随机部分),并且您必须考虑生日绑定,因为这在所有输入消息(cookie在你的情况下)。现在您可以说并尝试 2^64 中的一个碰撞机会,但无论如何您都需要一个 127 位计数器;这会让你在计数器溢出之前只剩下一个位;即您的 cookie 大小将最大化为 32 字节。

我个人不喜欢使用完整的 128 位,因为这实际上会增加冲突的机会。我会尝试确定 cookie 的最大大小,将其除以 16 - 向上舍入 - 以确定块的数量,然后将所需的位数保持为 0 以适应该(无符号)数字。然后你可以用随机位填充其他(最左边,最低索引)字节。 Cookie 的最大大小为 4096 字节,因此您可以很容易地看到它可以使用一字节计数器。


您可以使用生日攻击here 的算法来计算特定随机数大小的冲突机会(以位为单位的随机数大小为 log_2(H),因为 H 是 Wikipedia 文章中的空格)。然后,您可以使使用的字节数尽可能少,并计算重复计数器值的风险。

假设您可以接受 1/2^32 的碰撞风险,并且您预计不会超过 40 亿 (~2^24) 个 cookie。我使用in WolframAlpha 的计算是log_2(n^2 / (2p)) where p = 1 / 2^32, n = 2^24。然后可以使用大小为 79 位的 nonce 值;让我们将其四舍五入为 80 位或 10 个字节。换句话说,在 Java 中,您将创建一个 16 字节的 IV,并用 10 字节的安全随机数据填充最低索引字节(即计数器的最高有效位,因为 CTR 模式通常是大端)。

由于只增加了 cookie 的最高索引字节,因此您将有 5 个备用字节,如果最大 cookie 大小有所增加,那就太好了。

【讨论】:

【参考方案2】:

CTR 安全性要求您从不重复使用 IV 用于使用相同密钥的两个消息加密。实际上它更严格:CTR 模式通过加密计数器的连续值来工作(IV 只是该计数器的初始值),并且只有在不使用相同的计数器值两次时才能实现适当的安全性;这意味着使用 IV 加密一个值实际上会“消耗”一系列连续的 IV 值,这些值不能与另一个加密一起重复使用。

实现这一点的简单方法是使用加密安全随机数生成器,并为每条消息创建一个新的 16 字节随机 IV。我强调“加密安全”,因为这很重要;一个基本的随机数生成器是不够的。对于 Java,请使用 java.util.SecureRandom。使用 Win32,请致电 CryptGenRandom()。通过随机选择,可能的 128 位 IV 的空间足够大,以至于冲突极不可能发生。实际上,这就是 AES 使用 128 位块(因此暗示 128 位 IV)的原因。

将解密消息的实体必须知道 IV,因此您必须将其与加密消息一起存储。那是额外的 16 个字节。我知道这种开销是您想要避免的,尽管 16 个字节对于 cookie 来说并不算多。 cookie 的有效最大长度取决于 Web 浏览器,但 4000 个字符似乎“无处不在”。一个 16 字节的 IV,当以字符编码(例如使用 Base64)时,将使用大约 22 个字符,即远小于最大 cookie 大小的 1%:也许你能负担得起?

现在我们可以变得时髦并尝试通过诡计来减少 IV 长度:

使用散列函数生成 IV: 服务器端,使用计数器,从 0 开始,每次需要新的 IV 时递增。要获得 IV,请使用合适的散列函数对计数器进行散列,例如SHA-256,并且您保留哈希值的前 16 个字节。散列函数的“随机化属性”足以使 IV 在 CTR 要求方面具有足够的随机性。这需要一个加密安全的散列函数,因此需要 SHA-256(避免 MD5)。然后,您只需将计数器值存储在 cookie 中,计数器将小于 16 个字节(例如,如果您的客户不超过 40 亿,则计数器将适合 4 个字节)。但是,有一个隐藏的成本:服务器(我假设服务器正在您的系统中执行加密)必须确保它永远不会重用计数器值,因此它必须以一种持续存在的方式将“当前计数器”存储在某处服务器重新启动,如果您扩展到多个前端,也不会失败。这似乎并不容易。

使用外部唯一值:可能,cookie 可能是上下文的一部分,它提供足够的数据来生成一个值,该值对于每个加密都是唯一的。例如,如果请求还包含(明确的)“用户 ID”,您可以使用用户 ID 作为 IV 源。设置类似于上面的设置:您获取所有数据,将其填充到 SHA-256 中,SHA-256 输出的前 16 个字节就是您需要的 IV。仅当该数据对于给定的加密消息没有更改并且它确实是唯一的时,这才有效。这是一种罕见的情况:例如,“用户 ID”只有在永远不需要为同一用户重新加密新消息,并且永远不可能重用用户 ID(例如老用户退出,新用户出现并选择现在免费的用户 ID)。

使用通过加密安全 PRNG 生成的随机 16 字节 IV 仍然是“安全”的方式,也是我推荐的方式。如果您发现 cookie 中的空间紧张,那么这意味着您正在接近 4 kB 的限制,此时您可能需要使用压缩(在数据加密之前;加密之后,压缩非常非常不可能工作)。使用zlib(在Java中可以通过java.util.zip访问zlib)。

警告:在上述所有内容中,我并不是说任何关于 cookie 加密是否有助于提供您试图实现的任何安全特性。通常,当需要加密时,实际上既需要加密又需要完整性,然后应该使用加密和完整性组合的模式。查找 GCM 和 CCM。此外,cookie 加密主要有一个目的,即避免在服务器端存储一些用户特定数据的成本。如果您想为其他内容加密 cookie,例如验证一个有效的用户,那么你做错了:加密不是正确的工具。

【讨论】:

将数据的哈希值作为 IV 怎么样?这仍然需要与加密数据一起传输,但请求之间不需要服务器端资源,并且应该为不同的消息提供不同的 IV。还是这个方案有缺陷? 使用数据散列作为IV有两个可能的缺陷: 1.如果你加密两次相同的消息,那么你得到两次相同的加密结果,并且攻击者可以看到它。在您的特定环境中,这可能是也可能不是问题。 2. 发布消息的哈希值(作为 IV)允许攻击者对明文消息进行详尽的搜索:尝试可能的明文消息,直到找到匹配项。可能的明文消息通常比可能的密钥少得多。为了解决这个问题,IV 不应该是消息的散列,而应该是一个 MAC(带有 HMAC)。 如果您愿意,从消息本身计算 IV 是一个不错的想法,但需要小心。此外,它还可以防止流式加密:您必须先缓冲所有数据以对其进行哈希/M​​AC,然后才能开始加密。根据具体情况,这可能是也可能不是问题。 我对 IV 的长度有疑问:您建议 16 个字节,但不应该是整个计数器块的长度为 16 个字节(参见 RFC-3686)和 IV 的一部分那个计数器块?例如,这可以是 12 字节 IV 和 4 字节块计数器,或者,如果您查看 RFC-3686,则可以是 4 字节随机数、8 字节 IV 和 4 字节块计数器。谢谢和BR【参考方案3】:

可以通过使用 CBC 并将 HMAC 存储在消息前面来避免随机 IV。使用随机选取的常数 IV 是可以的。但是你必须确保消息都是不同的。

这是加密消息总是不同的情况。带有序列号的许可证密钥将符合此条件。带有用户 ID 或会话 ID 的 cookie 也会匹配。

如果您将 hmac 存储在消息前面,则可以使用带有随机常数 IV 的 CBC。哈希将累积第一个块中消息中传播的所有变化。如果您可以确保它是唯一的或在很长一段时间内不会重复使用,您还可以添加一些随机字节或最好添加一个序列号。

甚至不要考虑使用具有恒定 IV 的 CTR。

【讨论】:

【参考方案4】:

我没有直接回答你的问题,但有几件事要补充。

首先,加密 cookie 对我来说没有意义。如果您希望数据保密,则无论如何都不应该将其存储在 cookie 中。如果您想要完整性(即不可能篡改 cookie 的内容),您应该使用密钥哈希(例如 HMAC)。

另一个注意事项是永远不要使用全为 0 的 IV,只是为了方便。

IV 的大小与您的块相同。对于 AES-128,块大小为 128,密钥大小为 128,因此 IV 为 128 位。

最好的方法是创建一个随机的 AES 密钥并将其用作 IV。这个随机 IV 可能是公开的,只要它不会在后续使用相同密钥的加密中重复使用

编辑

您可能想查看此 wiki 页面以获取有关使用哪种模式的更多信息。但是,除非您确定应该使用它,否则切勿使用 ECB。即便如此,请与专家核实。据我所知,CBC 是最安全的(与 PCBC 一起)。

http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation

【讨论】:

确实,CTR 模式根本不提供完整性保护 - 可以翻转明文的任何选定位,因此如果已知明文包含 admin=0,则转换admin=1 是微不足道的。如果您想要完整性,请使用 HMAC。 为什么你会选择 CTR 模式而不是 CBC?我认为 CTR 比 CBC 更难搞砸【参考方案5】:

在 cookie 中包含一个大的随机数。 64 或 128 位的数字可能足够大。它需要足够大,以便很难获得重复。一定要在这个数字中加入足够的熵。不要只使用 gettime()。如果您有权访问 CRNG,请在此处使用它。

在您的应用程序中存储一个 256 位主密钥。使用 SHA256 导出您的密钥信息。同样,为此使用 CRNG。

$keyblob = sha256( concat("aeskeyid", $masterkey , $randomnumberwithcookie ) )
$aeskey = $keyblob[0..15]
$aesiv = $keyblob[16..31]

您可能还想为 HMAC 派生密钥。

$mackeyblob = sha256( concat("hmackeyid", $masterkey , $randomnumberwithcookie ) )

或者,您可以使用 SHA512 将上述两个哈希运算合二为一。

$keyblob = sha512( concat("randomkeyid", $masterkey , $randomnumberwithcookie ) )
$aeskey = $keyblob[0..15]
$aesiv = $keyblob[16..31]
$hmackey = $keyblob[32..63] 

【讨论】:

【参考方案6】:

如果您不使 IV 随机化(即,您使用一些重复的数字组),如果 cookie 始终以相同的明文开头,则更容易找出密钥。

AES-128 的 IV 大小为 128 位。 IIRC,IV 与密码块的大小相同。 128 位是 16 个字节。如果将其存储为 ASCII 十六进制字符串,则为 32 个字节。这真的太多了吗? 32字节在当今时代根本不算多……

【讨论】:

以上是关于如何为 AES/CTR/NoPadding 选择合适的 IV(初始化向量)?的主要内容,如果未能解决你的问题,请参考以下文章

新的 Firebase Firestore DocumentDb 如何为大型子集合建模

ActiveScaffold:如何为多态关联创建下拉选择?

如何为表单选择做可访问的内联错误?

如何为范围定义的标准优化复合选择?

如何为“选择”框制作占位符?

如何为整个作业选择打印机?