这是一个非常常见的异常,但显然我找到的解决方案都没有解决我的问题。
我有一个加密和解密方法;我加密字符串并将其写入文件,然后从文件中读取字符串并对其进行解密(理论上)。实际上,我得到一个
CryptographicException:要解密的数据长度无效
在进程的解密端。
下面是完成所有工作的Main()
方法:
public static void Main()
{
var filename = "test.encrypted";
var plainText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
string password = "A better password than this";
string salt = "Sodium Chloride";
var padding = PaddingMode.Zeros; // I have tried every padding mode to no avail
var encrypted = Encrypt<AesManaged>(plainText, password, salt, padding);
File.WriteAllBytes(filename, encrypted);
var fileBytes = File.ReadAllBytes(filename);
var decrypted = Decrypt<AesManaged>(fileBytes, password, salt, padding);
Console.ReadLine();
}
加密端:
static byte[] Encrypt<T>(string plainText, string password, string salt, PaddingMode padding)
where T : SymmetricAlgorithm, new()
{
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
algorithm.Padding = padding;
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
byte[] iv = new byte[algorithm.BlockSize >> 3];
RNGCryptoServiceProvider.Create().GetNonZeroBytes(iv);
var transform = algorithm.CreateEncryptor(key, iv);
using (MemoryStream buffer = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(cryptoStream, Encoding.Unicode))
{
writer.Write(plainText);
writer.Flush();
// cryptoStream.FlushFinalBlock() is called after writer.Flush()
// or as part of the Dispose().
// Calling it here causes a "you can't call that twice" exception.
// prepend IV to the data
return iv.Concat(buffer.ToArray()).ToArray();
}
}
}
…而另一面
static byte[] Decrypt<T>(byte[] encryptedData, string password, string salt, PaddingMode padding)
where T : SymmetricAlgorithm, new()
{
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
algorithm.Padding = padding;
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
//IV is at the beginning of the data
var iv = encryptedData.Take(algorithm.BlockSize >> 3).ToArray();
encryptedData = encryptedData.Skip(iv.Length).ToArray();
var transform = algorithm.CreateDecryptor(key, iv);
using (MemoryStream buffer = new MemoryStream())
using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
{
writer.Write(encryptedData);
writer.Flush();
return buffer.ToArray();
}
}
}
在我看来,最简单的方法是遵循MS模式,例如:
-
加密:
using (MemoryStream buffer = new MemoryStream()) using (CryptoStream cryptoStream = new CryptoStream(buffer, transform, CryptoStreamMode.Write)) { using (StreamWriter writer = new StreamWriter(cryptoStream, Encoding.Unicode)) { writer.Write(plainText); } return iv.Concat(buffer.ToArray()).ToArray(); }
-
解密:
using (MemoryStream buffer = new MemoryStream(encryptedData)) using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Read)) using (StreamReader reader = new StreamReader(stream, Encoding.Unicode)) { return Encoding.Unicode.GetBytes(reader.ReadToEnd()); }
使用这个构造,Close()
,FlushFinalBlock()
等的调用通过隐式的Dispose()
调用以正确的顺序触发(详细信息参见这里)。
旧的实现不是这种情况,它导致加密和解密不完整。此外,密文在解密过程中被Unicode(更准确地说是UTF-16LE)编码破坏(这是发布异常的原因)。
还要记住以下内容:
- 如果你以后unicode解码解密的数据(这是可能的),它是更有效的返回字符串而不是
Decrypt()
中的byte[]
(即return reader.ReadToEnd()
)。 - 关于填充,PKCS7填充应该使用而不是零填充,因为pkcs# 7填充更可靠(相对于你在代码中的注释)。 使用
StreamReader#.ReadToEnd()
可以防止与。net 6中破坏性更改相关的问题。您需要在Encrypt
方法中编码纯文本,并在.Write
中编码结果而不是纯文本。即ICryptoTransform
处理并返回加密后的输出字节数组的输入字节数组。
我建议去掉StreamWriter
,直接写CryptoStream
。
这是一个建议的修复。
static byte[] Encrypt<T>(string plainText, string password, string salt)
where T : SymmetricAlgorithm, new()
{
var plainTextBytes = Encoding.Unicode.GetBytes(plainText);
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
byte[] iv = new byte[algorithm.BlockSize >> 3];
RNGCryptoServiceProvider.Create().GetNonZeroBytes(iv);
using (var buffer = new MemoryStream())
using (var cryptoStream = new CryptoStream(buffer,
algorithm.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
return iv.Concat(buffer.ToArray()).ToArray();
}
}
}
// Return byte array if you need that and let the caller decode it.
static string Decrypt<T>(byte[] encryptedData, string password, string salt)
where T : SymmetricAlgorithm, new()
{
var saltBytes = Encoding.Unicode.GetBytes(salt);
var derivedBytes = new Rfc2898DeriveBytes(password, saltBytes);
using (var algorithm = new T())
{
var key = derivedBytes.GetBytes(algorithm.KeySize >> 3);
var iv = encryptedData.Take(algorithm.BlockSize >> 3).ToArray();
encryptedData = encryptedData.Skip(iv.Length).ToArray();
using (var buffer = new MemoryStream())
using (var stream = new CryptoStream(buffer,
algorithm.CreateDecryptor(key, iv), CryptoStreamMode.Write))
{
stream.Write(encryptedData, 0, encryptedData.Length);
stream.FlushFinalBlock();
return Encoding.Unicode.GetString(buffer.ToArray());
}
}
}
…在一些来电者中:
var filename = "test.encrypted";
var plainText = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
var password = "A better password than this";
var salt = "Sodium Chloride";
var encrypted = Encrypt<AesManaged>(plainText, password, salt);
File.WriteAllBytes(filename, encrypted);
var fileBytes = File.ReadAllBytes(filename);
var decrypted = Decrypt<AesManaged>(fileBytes, password, salt);
Console.WriteLine(decrypted == plainText);
请参阅此处的通用SymmetricCrypto<T>
类。
似乎有一个编码/流处理错误。您正在编写密文,就好像它是Unicode。我建议将密文包装在MemoryStream
中,然后使用输入CryptoStream
,即一个用于读取,然后一个StreamReader
用于读取文本。
现在你已经完全颠倒了流,从概念上讲,这是最有意义的。例如,假设您想直接从文件中获取密文。然后可以在读模式下使用FileStream
。您还可以决定对明文执行其他操作。当然,如果您首先有一个输入流,您可以简单地先读取IV,然后使用CryptoStream
继续读取。
最后,CryptoStream
现在可以清楚地发现流何时结束,因此可以执行最终操作,如自动取消填充,解决您的问题。