为什么一个错误的密码会导致“填充无效,无法删除”?
我需要一些简单的stringencryption,所以我写了下面的代码(从这里有很多“灵感”):
// 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()); }
代码似乎工作正常,除了解密数据时使用不正确的密钥,我得到CryptographicException – “填充是无效的,无法删除” – 在cs.Close()在decryptString行。
示例代码:
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
我的问题是,这是可以预料的吗? 我会认为用错误的密码解密只会导致无意义的输出,而不是一个例外。
虽然这已经得到了答复,但我认为解释为什么会这样做是一个好主意。
通常使用填充scheme,因为大多数encryptionfilter在语义上是不安全的并且防止某些forms的密码堆栈。 例如,通常在RSA中,使用OAEP填充scheme来防止某种types的攻击(例如select的明文攻击或致盲 )。
填充scheme在消息发送前附加一些(通常)随机垃圾到消息m中。 在OAEP方法中,例如,使用两个Oracles(这是一个简单的解释):
- 给定模数的大小,用0和k0位随机数填充k1位。
- 然后,通过对消息进行一些转换,您可以获得encryption并发送的填充消息。
这为您提供了一个随机的消息,并用一种方法来testing消息是否是垃圾。 由于填充scheme是可逆的,所以当你解密消息时,你不能说任何关于消息本身的完整性,事实上,你可以对填充做出一些断言,因此你可以知道消息是否被正确解密或者你做错了什么(即有人篡改了信息或者你使用了错误的密钥)
我遇到类似的“填充无效,不能删除”。 例外,但在我的情况下,关键IV和填充是正确的。
事实certificate,冲洗密码stream是所有的遗漏。
喜欢这个:
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();
如果你希望你的用法是正确的,你应该为你的密文添加authentication ,这样你就可以validation它是正确的密码或者密文没有被修改。 如果最后一个字节没有作为填充(0x01-0x10)的16个有效值之一解密,则使用ISO10126的填充将仅引发exception。 所以你有1/16的机会不会抛出exception的密码错误,如果你authentication它,你有一个确定性的方式来告诉你的解密是否有效。
使用encryptionAPI虽然看起来很容易,但实际上很容易犯错误。 例如,你使用一个固定的盐为你的密钥和iv的派生,这意味着每个用相同的密码encryption的密文将重复使用该密钥的IV,这打破了与CBC模式的语义安全性,IV需要是不可预知的和独特的一个给定的键。
出于这个容易出错的原因,我有一个代码片段,我试图保持审查和最新(评论,问题的欢迎):
C#string的对称身份validationencryption的现代示例。
如果在使用错误密码时使用AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
,则返回null
,如果密文或iv已被修改,则返回后encryptionnull
,则永远不会返回垃圾数据或填充exception。
是的,这是可以预料的,或者至less是当我们的encryption程序得到不可解密的数据时发生的事情
如果你排除了key-mismatch,那么除了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!
exception的另一个原因可能是使用解密逻辑的几个线程之间的竞争条件 – ICryptoTransform的本地实现不是线程安全的 (例如SymmetricAlgorithm),所以它应该放在独占节中,例如使用锁 。 请参阅这里的更多细节: http : //www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
CryptoStream中可能有一些未读的字节。 在完全读取stream之前closures在我的程序中导致错误。
我有一个类似的问题,解密方法的问题是初始化一个空的内存stream。 当我用这样的密文字节数组初始化它时,它工作:
MemoryStream ms = new MemoryStream(cipherText)
由用户“atconway”更新的答案为我工作。
问题不在于填充,而是在encryption和解密期间不同的密钥。 在encryption和解密相同的值时,密钥和iv应该相同。
我也得到填充是无效的,不能被删除的消息。 正如上面有人所说,原因是CryptoStream中的一些缓冲字节。 这是通过调用FlushFinalBlock()方法来解决的:
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write)) { cryptoStream.Write(bytes, 0, bytes.Length); cryptoStream.FlushFinalBlock(); result = Encoding.UTF8.GetString(memoryStream.ToArray()); return result; }