有两种方法可以为RijndaelManaged对象指定键和IV。一种方法是调用CreateEncryptor
var encryptor = rij.CreateEncryptor(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv)));另一种是直接设置Key和IV属性:
rij.Key = "1111222233334444";
rij.IV = "1111222233334444";只要Key和IV的长度是16字节,这两种方法就会产生相同的结果。但是,如果您的密钥小于16个字节,则第一种方法仍然允许您对数据进行编码,而第二种方法会因异常而失败。
现在,这听起来可能是一个绝对抽象的问题,但我必须使用PHP &密钥,它只有10个字节长,以便将加密消息发送到使用第一种方法的服务器。
所以问题是:CreateEncryptor是如何扩展键的,有没有实现?我不能修改C#代码,所以我不得不在PHP中复制这种行为。
发布于 2013-09-26 07:02:45
我将不得不从一些假设开始。(TL;DR -解决方案大约是下降的三分之二,但旅程要凉爽得多)。
首先,在您的示例中,您将IV和Key设置为字符串。这是不可能的。因此,我假设我们在字符串上调用GetBytes(),顺便说一句,这是一个糟糕的想法,因为在可用的ASCII空间中,潜在的字节值比一个字节中的全部256个值要少;这就是GenerateIV()和GenerateKey()的用途。我会在最后讲到这一点。
接下来,我将假设您使用RijndaelManaged的默认块、密钥和反馈大小:分别为128、256和128。
现在我们将反编译Rijndael CreateEncryptor()调用。当它创建Transform对象时,它根本不会对键做任何事情(除了设置m_Nk,我稍后会介绍它)。相反,它直接从给定的字节生成键扩展。
现在它变得有趣了:
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 8128 / 8 = 16,所以如果len(k)是16,我们可以期望打开len(k) x 8。如果它更大,那么它也会打开len(k) x 8。如果它小于,它将切换到块大小,128。
有效的开关值为128、192和256。这意味着只有当它的长度超过16字节并且不是某种有效的块(不是键)长度时,它才会被设置为默认值(并抛出异常)。
换句话说,它从不检查RijndaelManaged对象中指定的密钥长度。它直接进入密钥扩展,并开始在块级别操作,只要密钥长度(以位为单位)是128、192、256或小于128之一。这实际上是对块大小的检查,而不是对密钥大小的检查。
那么,如果我们显然没有检查密钥长度,会发生什么呢?答案与密钥调度的性质有关。在Rijndael中输入密钥时,需要展开密钥才能使用。在本例中,它将扩展到176字节。为了实现这一点,它使用了一种专门设计的算法,将短字节数组转换为更长的字节数组。
其中一部分涉及到检查密钥长度。更多的反编译乐趣,我们发现这被定义为m_Nk。听起来耳熟吗?
this.m_Nk = rgbKey.Length / 4;对于16字节的密钥,Nk为4,当我们输入较短的密钥时,Nk会更小。这是4个单词,对于那些想知道魔术数字4从何而来的人来说。这在密钥调度程序中导致了一个奇怪的分支,对于Nk <= 6有一个特定的路径。
没有太深入的细节,这实际上是“工作”(即。不是在火球中崩溃)密钥长度小于16个字节...直到它低于8个字节。
然后整个事情就会轰然倒塌。
那么我们学到了什么呢?当你使用CreateEncryptor时,你实际上是把一个完全无效的密钥直接扔到密钥调度器中,幸运的是,有时它不会直接崩溃(或者是一个可怕的合同完整性破坏,这取决于你的观点);可能是一个意外的副作用,因为有一个特定的分支来处理短密钥长度。
为了完整起见,我们现在可以看看在RijndaelManaged对象中设置键和IV的另一个实现。它们存储在SymmetricAlgorithm基类中,该类具有以下设置器:
if (!this.ValidKeySize(value.Length * 8))
throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKeySize"));对啰。合同得到正确执行。
一个显而易见的答案是,你不能在另一个库中复制它,除非那个库碰巧包含同样明显的问题,我将其称为微软代码中的错误,因为我真的看不到任何其他选择。
但答案是推卸责任。通过检查密钥调度程序,我们可以弄清楚实际发生了什么。
当扩展键被初始化时,它用0x00s填充自身。然后,它使用我们的密钥写入第一个Nk字(在我们的例子中,Nk = 2,因此它填充前2个字或8个字节)。然后,它通过填充超出该点的扩展键的其余部分,进入扩展的第二阶段。
所以现在我们知道它本质上是用0x00填充所有超过8个字节的内容,我们可以用0x00填充它,对吗?不会,因为这会将Nk上移到Nk = 4。因此,尽管我们的前4个字(16字节)将如我们预期的那样填充,但第二阶段将从第17个字节开始扩展,而不是第9个字节!
那么解决方案就变得微不足道了。而不是用6个额外的字节填充我们的初始键,只要去掉最后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也就是每秒4161615次。218,340,105,584,896 / 4,161,615 / 3600 / 24 = 607天。
好吧,607天还不错。但我总是可以启动一堆Amazon服务器,并通过要求607个等效实例来计算1/607的搜索空间,从而将这一时间缩短到~1天。那要花多少钱?不到1000美元,假设每个实例的效率只有我忙碌的笔记本电脑的效率。更便宜,更快。
还有一个实现的速度是openssl1的两倍,所以把我们最终得到的数字减半。
然后我们必须考虑,在耗尽整个搜索空间之前,我们几乎肯定会找到关键字。所以据我们所知,它可能会在一个小时内完成。
在这一点上,我们可以断言如果数据值得加密,那么破解密钥可能是值得的。
这就对了。
https://stackoverflow.com/questions/15260399
复制相似问题