gpt4 book ai didi

php - RijndaelManaged.CreateEncryptor key 扩展

转载 作者:行者123 更新时间:2023-12-04 05:08:03 26 4
gpt4 key购买 nike

有两种方法可以为 RijndaelManaged 指定 key 和 IV。对象。一种是调用CreateEncryptor :

var encryptor = rij.CreateEncryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv)));

另一个是直接设置 KeyIV属性:
rij.Key = "1111222233334444";
rij.IV = "1111222233334444";

只要长度 KeyIV是 16 个字节,两种方法产生相同的结果。但是,如果您的 key 短于 16 个字节,则第一种方法仍然允许您对数据进行编码,而第二种方法会失败并出现异常。

现在这听起来像是一个绝对抽象的问题,但我必须使用 PHP & key 只有 10 个字节长,以便将加密消息发送到使用第一种方法的服务器。

那么问题来了: CreateEncryptor怎么做?展开 key ,是否有 PHP 实现?我无法更改 C# 代码,因此我不得不在 PHP 中复制此行为。

最佳答案

我将不得不从一些假设开始。 (TL;DR - 解决方案是大约三分之二的路程,但旅程更酷)。

首先,在您的示例中,您将 IV 和 Key 设置为字符串。这是做不到的。因此,我将假设我们在字符串上调用 GetBytes() ,顺便说一下,这是一个糟糕的主意,因为可用 ASCII 空间中的潜在字节值比一个字节中的所有 256 个值少;这就是 GenerateIV() 和 GenerateKey() 的用途。我会在最后完成这个。

接下来,我将假设您使用 RijndaelManaged 的​​默认块、 key 和反馈大小:分别为 128、256 和 128。

现在我们将反编译 Rijndael CreateEncryptor() 调用。当它创建 Transform 对象时,它根本不使用键做任何事情(除了设置 m_Nk,我稍后会谈到)。相反,它直接从给定的字节生成 key 扩展。

现在它变得有趣了:

switch (this.m_blockSizeBits > rgbKey.Length * 8 ? this.m_blockSizeBits : rgbKey.Length * 8)

所以:
128 > len(k) x 8 = 128
128 <= len(k) x 8 = len(k) x 8

128/8 = 16,所以如果 len(k) 是 16,我们可以期望打开 len(k) x 8。如果它更多,那么它也会打开 len(k) x 8。如果它更小,它将打开块大小,128。

有效的开关值为 128、192 和 256。这意味着如果它的长度超过 16 个字节并且不是某种有效的块(不是键)长度,它只会落入默认值(并抛出异常)。

换句话说,它从不检查 RijndaelManaged 对象中指定的 key 长度。它直接进入 key 扩展并在块级别开始操作,只要 key 长度(以位为单位)是 128、192、256 或小于 128 之一。这实际上是对块大小的检查,而不是 key 大小。

那么现在我们显然没有检查 key 长度会发生什么?答案与 key 时间表的性质有关。当您在 Rijndael 中输入 key 时,该 key 需要展开才能使用。在这种情况下,它将被扩展到 176 字节。为了实现这一点,它使用了一种专门设计用于将短字节数组转换为更长字节数组的算法。

其中一部分涉及检查 key 长度。更多的反编译乐趣,我们发现它定义为 m_Nk。听起来很熟悉?
this.m_Nk = rgbKey.Length / 4;

对于 16 字节的 key ,Nk 是 4,当我们输入较短的 key 时会减少。这是 4 个词,对于任何想知道神奇数字 4 来自何处的人来说。这会导致 key 调度程序中出现奇怪的 fork ,Nk <= 6 有一个特定的路径。

在不深入细节的情况下,这实际上发生在 key 长度小于 16 字节的“工作”(即不会在火球中崩溃)......直到它低于 8 字节。

然后整个事情发生了惊人的崩溃。

那么我们学到了什么?当您使用 CreateEncryptor 时,您实际上是将一个完全无效的 key 直接扔到了 key 调度程序中,而且有时它不会彻底崩溃(或严重违反契约(Contract)完整性,取决于您的 POV),这是一个偶然的机会;可能是一个意外的副作用,因为短 key 长度有一个特定的 fork 。

为了完整起见,我们现在可以查看在 RijndaelManaged 对象中设置 Key 和 IV 的另一个实现。这些存储在 SymmetricAlgorithm 基类中,该基类具有以下 setter :
if (!this.ValidKeySize(value.Length * 8))
throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKeySize"));

宾果游戏。契约(Contract)得到妥善执行。

显而易见的答案是,您不能在另一个库中复制它,除非该库碰巧包含相同的明显问题,我将称其为 Microsoft 代码中的错误,因为我真的看不到任何其他选项。

但那个答案将是一个警察。通过检查关键调度程序,我们可以弄清楚实际发生了什么。

当扩展的键被初始化时,它会用 0x00s 填充自己。然后它用我们的 key 写入前 Nk 个字(在我们的例子中 Nk = 2,所以它填充前 2 个字或 8 个字节)。然后它通过填充超出该点的其余扩展 key 进入扩展的第二阶段。

所以现在我们知道它本质上是用 0x00 填充超过 8 个字节的所有内容,我们可以用 0x00s 填充它,对吗?不;因为这会将 Nk 向上移动到 Nk = 4。因此,尽管我们的前 4 个字(16 个字节)将按预期填充,但第二阶段将在第 17 个字节而不是第 9 个字节处开始扩展!

那么解决方案是完全微不足道的。而不是用 6 个额外的字节填充我们的初始 key ,只需砍掉最后 2 个字节。

所以你在 PHP 中的直接回答是:
$key = substr($key, 0, -2);

很简单吧? :)

现在您可以与此加密功能互操作。但是不要。它可以被破解。

假设您的键使用小写、大写和数字,您的搜索空间只有 218 万亿个键。

62 字节 (26 + 26 + 10) 是每个字节的搜索空间,因为您从不使用其他 194 (256 - 62) 个值。由于我们有 8 个字节,因此有 62^8 种可能的组合。 218 万亿。

我们可以多快尝试该空间中的所有键?让我们问 openssl 我的笔记本电脑(运行很多杂乱)可以做什么:
Doing aes-256 cbc for 3s on 16 size blocks: 12484844 aes-256 cbc's in 3.00s

那是 4,161,615 次传球/秒。 218,340,105,584,896/4,161,615/3600/24 = 607 天。

好吧,607天还不错。但是我总是可以启动一堆 Amazon 服务器,然后通过要求 607 个等效实例计算搜索空间的 1/607 来将其缩短到大约 1 天。那要花多少钱?不到 1000 美元,假设每个实例的效率仅与我忙碌的笔记本电脑一样有效。否则更便宜更快。

还有一个实现速度是openssl的两倍 1 ,所以把我们最终得到的任何数字减半。

然后我们必须考虑到我们几乎肯定会在耗尽整个搜索空间之前找到 key 。所以就我们所知,它可能会在一个小时内完成。

在这一点上,我们可以断言数据是否值得加密,破解 key 可能是值得的。

所以你去。

关于php - RijndaelManaged.CreateEncryptor key 扩展,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15260399/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com