1

我知道尝试实现自己的加密算法是个坏主意。这是我认为不是的原因之一:

例如,假设我想发送plainText = new byte[]{2,5,1,6,7}给 bob。

如果我使用 AES 加密或其他众所周知的算法,那么我将拥有:

cipherText = Aes.Encrypt(plainText, key, iv); // now I have some cipher text that is not readable to anyone if they do not have the key and iv vector.

如果有人想解密该消息,那么他们将不得不执行以下操作:

newPlainText = Aes.Decrypt(cipherText, key, iv);

现在我的问题是 AES 如何知道您是否输入了正确的密钥?我认为拥有一种不会立即进行暴力攻击的算法会更安全。换句话说,如果黑客知道你使用了 AES,它可以继续尝试大量密码,直到method Aes.Decrypt没有例外。现在考虑这个算法:

假设我想向{2,5,1,6,7}bob 发送相同的字节数组。我的加密算法可能如下所示:

密码是 = "securePassword";

我将遍历纯文本中的每个字节,并对密码中每个字符的 ASCII 值执行 Xor 运算符。例如,第一个字节将是2 Xor (ASCII value of 's')然后下一个值将5 Xor (ASCII value of 'e')在最后我将结束{2 Xor 's', 5 Xor 'e', 1 Xor 'c', 6 Xor 'u', 7 Xor 'r'} 这个算法的好处是,如果你有正确的密钥,你永远不会现在!

使用此算法,无法知道您是否拥有写入密钥,这使得我相信无法破译它。如果您使用众所周知的算法,如果您的密码不是很长,您会提示进行暴力攻击。

*所以我的问题是,众所周知的对称加密算法(如 AES)如何知道您输入的密钥是否正确?拥有一个您不知道是否提供正确密钥的算法不会更安全吗?*

4

3 回答 3

9

加密算法知道输入密钥是否正确!该算法可以很好地使用任何密钥解密;如果您使用错误的密钥,它将最终变成垃圾。一旦完成,使用加密的应用程序可能能够检测到这一点,因为解密的消息可能会有无效的填充或以其他方式格式错误,但 AES 本身永远不会“抛出异常”。

您描述的算法是一次性垫。它的致命缺陷是它的密钥必须至少和消息一样长,并且永远不能重复使用——如果相同的密钥与两条消息一起使用,那么这两条消息A ⊕ K可以B ⊕ K异或一起产生A ⊕ K ⊕ B ⊕ K = A ⊕ B——密钥有现在已从消息中删除,并且可能可以从中猜出消息是什么。

于 2013-07-03T17:00:12.290 回答
3

首先,AES 算法就是这样。这只是一个数学密码。它无法知道您是否使用了正确的键。提供这种能力的区别通常是密码原语与密钥、IV 和明文/密文的一个或多个“块”组合以产生结果的预定义方式;这称为密码模式。

最常见的模式之一是 CBC,或密码块链接。在这种模式下,先前加密的密文块在通过密码运行之前与下一个明文块进行异或(第一个块与 IV 进行异或)。这种模式要求明文消息长度是块大小的精确倍数,这是通过填充来实现的。

填充与加密的“链接”模式相结合,提供了各种校验和。最后一个块必须是一个有效填充的块(对于恰好是块大小的精确长度倍数的消息,最后一个块仍然是填充;它只是全零)。如果不是,则要么使用了错误的密钥,要么消息在传输过程中被破坏(加密的链接效应意味着任何引入密文的错误都可以在该明文块和以后的每个块中看到)。当使用错误的键时,这很可能是您的异常的来源。然而,CBC 已被证明容易受到选择密文攻击,如果允许攻击者将经过特殊修改的密文块输入解密算法并查看结果,则允许攻击者在不知道密钥的情况下恢复明文。

其他模式具有内置消息身份验证,可防止此类攻击。例如,有 CCM,即带有 CBC-MAC 的计数器。首先,通过 CBC 加密对消息进行哈希处理,但只保留最后一个数据块(还记得级联行为吗?事实证明这是计算密钥哈希的好方法)。然后,消息和散列摘要使用计数器模式加密(IV 与顺序值异或,产生与每个明文块异或的随机数)。如果使用了错误的密钥,或者消息在传输过程中发生了变化,那么在重新散列时,生成的消息将与散列摘要不匹配。除非你确定你有正确的钥匙,否则真的不可能知道是哪种情况,

于 2013-07-03T17:04:34.343 回答
1

But to answer your question, the .NET AES provider does know if you're using the wrong key - but that's only because you asked it to.

You tried to encrypt the bytes:

2,5,1,6,7

Using code something like:

var plainText = new byte[] {2,5,1,6,7};
byte[] cipherText;

var key = new Byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
var iv = new Byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

//encrypt plaintext using one key
using (var aes = Aes.Create())
{
   var crypt = aes.CreateEncryptor(key, iv);
   cipherText = crypt.TransformFinalBlock(plainText, 0, plainText.Length);
}

In reality, AES only operates on 16-byte blocks. In order to transform your input data into something long enough that it can actually encrypt, the input bytes are first padded. When you construct an Aes class in .NET:

using (var aes = Aes.Create())
{
}

the default padding mode in Aes is PKCS#7 padding. This causes the Aes encrypted to pad your plainText to:

2, 5, 1, 6, 7, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11

And those byes are then encrypted into:

210, 49, 160, 164, 2, 53, 121, 254, 79, 249, 91, 111, 104, 173, 50, 207

When you decrypt those cipher bytes:

byte[] recoveredPlainText;
using (var aes = Aes.Create())
{
   var key = new Byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
   var iv = new Byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
   var crypt = aes.CreateDecryptor(key, iv);
   recoveredPlainText = crypt.TransformFinalBlock(cipherText, 0, cipherText.Length);
}

Aes knows to ensure that proper PKCS#7 padding exists:

2, 5, 1, 6, 7 , 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11

If you tried to decrypt with an invalid key (e.g. {1,0,0, ..., 0}), you will end up with plainText that does not contain valid PKCS#7 padding:

154,203,183,159,52,162,186,41,127,162,152,75,114,109,107,74

And so the Aes decryptor will throw an exception.

You can still decrypt the bytes with an invalid key, all you have to do is tell Aes not to look for any padding by setting Padding = PaddingMode.None:

byte[] recoveredPlainText;
using (var aes = Aes.Create())
{
   aes.Padding = PaddingMode.None;
   var key = new Byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
   var iv = new Byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
   var crypt = aes.CreateDecryptor(key, iv);
   recoveredPlainText = crypt.TransformFinalBlock(cipherText, 0, cipherText.Length);
}

tl;dr: It knows because you used Padding.

Note: Any code is released into the public domain. No attribution required.

于 2013-07-03T20:29:01.453 回答