我需要一些简单的字符串加密,所以我编写了以下代码(来自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());
}
代码似乎工作正常,除了在用不正确的密钥解密数据时,我在decryptString中的cs.Close()行上得到CryptographicException - “Padding无效且无法删除”。
示例代码:
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
我的问题是,这是预期的吗?我原本以为用错误的密码解密只会导致无意义的输出,而不是异常。
答案 0 :(得分:27)
虽然已经回答了这个问题,但我认为解释为什么是一个好主意。
通常应用填充方案,因为大多数加密过滤器在语义上不安全并且防止某些形式的加密包。例如,通常在RSA中使用OAEP填充方案来防止某些类型的攻击(例如选择的明文攻击或blinding)。
填充方案在发送消息之前将一些(通常)随机垃圾附加到消息m。例如,在OAEP方法中,使用了两个Oracles(这是一个简单的解释):
它为您提供了消息的随机化,并提供了一种测试消息是否为垃圾的方法。由于填充方案是可逆的,当您解密消息而您无法说明消息本身的完整性时,您实际上可以对填充做出一些断言,因此您可以知道消息是否已被正确解密或者你做错了什么(即有人篡改了消息或你使用了错误的密钥)
答案 1 :(得分:17)
我遇到了类似的“填充无效且无法删除”。异常,但在我的情况下,密钥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();
答案 2 :(得分:5)
如果您希望您的用法正确,则应将authentication添加到密文,以便您可以验证它是正确的密码,还是密文未被修改。如果最后一个字节没有作为填充的16个有效值之一(0x01-0x10)解密,则使用ISO10126的填充只会抛出异常。因此,您有1/16的机会不会使用错误的密码抛出异常,如果您对其进行身份验证,则可以通过确定的方式判断您的解密是否有效。
使用crypto api虽然看似简单,但实际上很容易犯错误。例如,你使用固定的盐来进行密钥和静脉推导,这意味着每个使用相同密码加密的密文将重用它的IV与该密钥,这会破坏CBC模式的语义安全性,IV需要既不可预测又独特。给定的密钥。
由于这个容易出错的原因,我有一个代码片段,我会尽量保持审核和更新(评论,欢迎提出问题):
Modern Examples of Symmetric Authenticated Encryption of a string C#.
如果在使用错误密码时使用AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
,则会返回null
,如果在加密null
后修改了密文或iv,则永远不会得到垃圾邮件数据返回或填充异常。
答案 3 :(得分:4)
如果您排除了密钥错配,那么除FlushFinalBlock()
之外(请参阅Yaniv的答案),在Close()
上调用CryptoStream
也足够了。
如果要使用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!
答案 4 :(得分:3)
是的,这是预期的,或者至少是我们的加密例程获得不可解密的数据时发生的事情
答案 5 :(得分:2)
异常的另一个原因可能是使用解密逻辑的多个线程之间的竞争条件 - ICryptoTransform的本机实现不是线程安全的(例如SymmetricAlgorithm),因此它应该被置于独占部分,例如使用 lock 。 有关详细信息,请参阅此处:http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
答案 6 :(得分:1)
CryptoStream中可能有一些未读的字节。在完全读取流之前关闭导致我的程序出错。
答案 7 :(得分:0)
我遇到了类似的问题,解密方法中的问题是初始化一个空的内存流。当我使用密文文本字节数组初始化它时它工作,如下所示:
MemoryStream ms = new MemoryStream(cipherText)
答案 8 :(得分:-2)
用户“atconway”更新的答案对我有用。
问题不在于填充,而是在加密和解密期间不同的密钥。 在加密和解密相同的值时,键和iv应该相同。