我需要一些简单的字符串加密,所以我写了下面的代码(从这里有很多"灵感" ):
// 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
我的问题是,这是预期的吗?我原本以为用错误的密码解密只会导致无意义的输出,而不是异常.
虽然这已经得到了回答,但我认为解释为什么会出现这种情况是个好主意.
通常应用填充方案,因为大多数加密过滤器在语义上不安全并且防止某些形式的加密包.例如,通常在RSA中使用OAEP填充方案,其防止某些类型的攻击(例如选择的明文攻击或盲目).
填充方案在发送消息之前将一些(通常)随机垃圾附加到消息m.例如,在OAEP方法中,使用了两个Oracles(这是一个简单的解释):
给定模数的大小,您将k1位用0和k0位用随机数填充.
然后通过对消息应用一些转换,您将获得加密并发送的填充消息.
这为您提供了消息的随机化,并提供了一种测试消息是否为垃圾的方法.由于填充方案是可逆的,当您解密消息而您无法说明消息本身的完整性时,您实际上可以对填充做出一些断言,因此您可以知道消息是否已被正确解密或者你做错了什么(即有人篡改了消息或你使用了错误的密钥)
我经历了类似的"填充无效,无法删除".异常,但在我的情况下,键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();
如果您希望您的用法正确,您应该为您的密文添加身份验证,以便您可以验证它是正确的密码还是密文未被修改.如果最后一个字节没有作为填充的16个有效值之一(0x01-0x10)解密,则使用ISO10126的填充只会抛出异常.因此,您有1/16的机会不会使用错误的密码抛出异常,如果您对其进行身份验证,则可以通过确定的方式判断您的解密是否有效.
使用crypto api虽然看似简单,但实际上很容易犯错误.例如,你使用固定的盐来进行密钥和静脉推导,这意味着每个使用相同密码加密的密文将重用它的IV与该密钥,这会破坏CBC模式的语义安全性,IV需要既不可预测又独特.给定的密钥.
由于这个容易出错的原因,我有一个代码片段,我尝试不断检查和更新(评论,欢迎提出问题):
字符串C#的对称认证加密的现代例子.
如果您在使用AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
错误密码时使用它,null
则会返回,如果在加密后修改了密文或iv null
,则永远不会收到垃圾数据或填充异常.