为啥密码错误会导致“填充无效,无法删除”?
Posted
技术标签:
【中文标题】为啥密码错误会导致“填充无效,无法删除”?【英文标题】:Why does a bad password cause "Padding is invalid and cannot be removed"?为什么密码错误会导致“填充无效,无法删除”? 【发布时间】:2010-09-05 22:06:34 【问题描述】:我需要一些简单的字符串加密,所以我编写了以下代码(来自here 的大量“灵感”):
// create and initialize a crypto algorithm
private static SymmetricAlgorithm getAlgorithm(string password)
SymmetricAlgorithm algorithm = Rijndael.Create();
Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
password, new byte[]
0x53,0x6f,0x64,0x69,0x75,0x6d,0x20, // salty goodness
0x43,0x68,0x6c,0x6f,0x72,0x69,0x64,0x65
);
algorithm.Padding = PaddingMode.ISO10126;
algorithm.Key = rdb.GetBytes(32);
algorithm.IV = rdb.GetBytes(16);
return algorithm;
/*
* encryptString
* provides simple encryption of a string, with a given password
*/
public static string encryptString(string clearText, string password)
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
/*
* decryptString
* provides simple decryption of a string, with a given password
*/
public static string decryptString(string cipherText, string password)
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] cipherBytes = Convert.FromBase64String(cipherText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
return System.Text.Encoding.Unicode.GetString(ms.ToArray());
该代码似乎工作正常,除了使用不正确的密钥解密数据时,我在解密字符串的 cs.Close() 行上收到 CryptographicException - “填充无效且无法删除”。
示例代码:
string password1 = "password";
string password2 = "letmein";
string startClearText = "The quick brown fox jumps over the lazy dog";
string cipherText = encryptString(startClearText, password1);
string endClearText = decryptString(cipherText, password2); // exception thrown
我的问题是,这是意料之中的吗?我原以为用错误的密码解密只会导致无意义的输出,而不是异常。
【问题讨论】:
您的评论为我节省了很多时间:"The code appears to work fine, except that when decrypting data with an incorrect key"
我发誓我复制了密钥,但看起来我没有复制 2x。希望这在查看填充机制或更改代码之前对其他人有所帮助。
【参考方案1】:
虽然已经回答了这个问题,但我认为最好解释一下为什么会出现这种情况。
通常会应用填充方案,因为大多数加密过滤器在语义上并不安全,并且可以防止某些形式的加密攻击。例如,通常在 RSA 中使用OAEP 填充方案来防止某些类型的攻击(例如选择明文攻击或blinding)。
在发送消息之前,填充方案会在消息 m 上附加一些(通常)随机垃圾。在OAEP方法中,例如使用了两个Oracle(这是一个简单的解释):
-
考虑到模数的大小,您用 0 填充 k1 位,用随机数填充 k0 位。
然后,通过对消息应用一些转换,您可以获得经过加密和发送的填充消息。
这为您提供了消息的随机化,并提供了一种测试消息是否垃圾的方法。由于填充方案是可逆的,当您解密消息时,虽然您无法说明消息本身的完整性,但实际上您可以对填充做出一些断言,因此您可以知道消息是否已正确解密或者您做错了什么(即有人篡改了消息或您使用了错误的密钥)
【讨论】:
豪尔赫,感谢您的解释。我看到了与描述相同的行为,解密的数据是正确的。我应该吃这个例外,还是(希望)我做错了什么我可以纠正?抛出异常时出了什么问题?我读过的所有帖子似乎都是由对消除异常更感兴趣的人写的。就我而言,我希望我的用法正确:) 这没有回答 OP 的问题,问题是关于对称分组密码 Rijndael,而不是 RSA 等非对称密码。对于块密码,添加填充以使要加密的数据成为块大小的倍数,通常使用 PKCS#7(née PKCS#5)。通过这些填充方案,随机字节不被添加。请注意,填充 ISO 10126 已被撤销,随机字节没有增加任何安全性。如果它涉及对称加密填充,答案将是重点。建议:固定答案以回答问题。 ZOMG,高级开发人员。 @zaph 我只是笼统地解释为什么在使用无效密码解密时会得到无效填充异常而不是垃圾。 RSA 只是一个示例,使用的特定填充方案是示例。它回答了操作问题,因为它说:是的,这是预期的,这就是为什么以及关于那个奇怪的“填充”参考的解释,只有当你了解填充方案是什么以及为什么使用它时,你才能理解,但会让你如果你不这样做会感到困惑(加密与填充有什么关系???) 问题是关于 Rijndael 填充,您解释了 RSA 填充,它们非常不同并且出于不同的原因而完成。好的,我知道人们对对称加密和非对称加密之间的区别感到困惑,并且 RSA 最近似乎更加明显,当 AES 更适合时,许多人正在使用 RSA。但如果答案适合问题会更好。【参考方案2】:我遇到了类似的“填充无效,无法删除”。例外,但在我的情况下,密钥 IV 和填充是正确的。
事实证明,只缺少刷新加密流。
像这样:
MemoryStream msr3 = new MemoryStream();
CryptoStream encStream = new CryptoStream(msr3, RijndaelAlg.CreateEncryptor(), CryptoStreamMode.Write);
encStream.Write(bar2, 0, bar2.Length);
// unless we flush the stream we would get "Padding is invalid and cannot be removed." exception when decoding
encStream.FlushFinalBlock();
byte[] bar3 = msr3.ToArray();
【讨论】:
encStream.Close();
也应该这样做。
也许你应该在这些对象上使用using
,因为它们是一次性的。这样就够了。
另请注意,您通常对流执行的简单Flush()
与CryptoStream
所需的FlushFinalBlock()
不相同。这在代码前面的描述中有点模棱两可。
这与问题无关,提问者知道异常的原因,密码错误(不刷新),并在询问为什么密码错误导致此特定异常。【参考方案3】:
如果您希望您的使用正确,您应该将authentication 添加到您的密文中,以便您可以验证它是正确的密码或者密文没有被修改。您使用的填充 ISO10126 只会在最后一个字节未解密为 16 个有效填充值之一 (0x01-0x10) 时引发异常。因此,您有 1/16 的机会不会使用错误的密码引发异常,如果您对其进行身份验证,您就有一种确定的方式来判断您的解密是否有效。
使用crypto api看似简单,实际上却很容易出错。例如,您为密钥和 iv 派生使用固定盐,这意味着使用相同密码加密的每个密文都将使用该密钥重用它的 IV,这会破坏 CBC 模式的语义安全性,IV 需要是不可预测的和唯一的给定的键。
出于容易出错的原因,我有一个代码 sn-p,我试图保持审查和更新(cmets,欢迎发布):
Modern Examples of Symmetric Authenticated Encryption of a string C#.
如果你使用AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
,密码错误,返回null
,如果密文或iv被修改后加密返回null
,你永远不会得到垃圾数据,或者填充例外。
【讨论】:
【参考方案4】:如果您已经排除了键不匹配,那么除了 FlushFinalBlock()
(请参阅 Yaniv 的回答)之外,在 CryptoStream
上调用 Close()
也足够了。
如果您严格使用 using
块清理资源,请务必为 CryptoStream
本身嵌套块:
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
encStream.Write(bar2, 0, bar2.Length);
// implicit close
byte[] encArray = ms.ToArray();
我被这个(或类似的)咬过:
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
encStream.Write(bar2, 0, bar2.Length);
byte[] encArray = ms.ToArray();
// implicit close -- too late!
【讨论】:
但是如果你这样做encStream.FlushFinalBlock()
我会假设你在哪里做 ms.ToArray()
并不重要(无论是在CryptoStream
的using
的外部还是内部)。
OP 知道这是导致错误的键不匹配,并询问为什么键不匹配会导致此错误。【参考方案5】:
是的,这是意料之中的,或者至少,当我们的加密例程获得不可解密的数据时会发生这种情况
【讨论】:
【参考方案6】:异常的另一个原因可能是使用解密逻辑的多个线程之间的竞争条件 - ICryptoTransform 的本机实现不是线程安全的(例如 SymmetricAlgorithm),所以它应该放在独占部分,例如使用锁定。 更多详情请参考这里:http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
【讨论】:
【参考方案7】:CryptoStream 中可能有一些未读字节。在完全读取流之前关闭导致我的程序出错。
【讨论】:
【参考方案8】:我有一个类似的问题,解密方法的问题是初始化一个空的内存流。当我用这样的密文字节数组初始化它时它起作用了:
MemoryStream ms = new MemoryStream(cipherText)
【讨论】:
【参考方案9】:用户“atconway”更新的答案对我有用。
问题不在于填充,而在于加密和解密期间的密钥不同。 在加密和解密相同的值时,密钥和 iv 应该相同。
【讨论】:
以上是关于为啥密码错误会导致“填充无效,无法删除”?的主要内容,如果未能解决你的问题,请参考以下文章