This is a really common exception, but obviously none of the solutions I've found have resolved my issue.
I have an Encrypt and a Decrypt method; I encrypt a string and write it to a file, then read the string from the file and decrypt it (in theory). In reality, I get a
CryptographicException: Length of the data to decrypt is invalid
on the decryption side of the process.
Here's the Main()
method that does all the work:
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();
}
The encrypt side:
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();
}
}
}
... and the flip side
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();
}
}
}
Imo the easiest way is to follow the MS pattern, e.g. here:
for encryption:
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();
}
and for decryption:
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());
}
With this construct the call of Close()
, FlushFinalBlock()
etc. is triggered via the implicit Dispose()
calls in the correct order (for details see also here).
This is not the case with the old implementation, which results in incomplete encryption and decryption. In addition, the ciphertext is corrupted by the Unicode (more precisely UTF-16LE) encoding during decryption (which is the cause of the posted exception).
Keep in mind also the following:
byte[]
in Decrypt()
(i.e. return reader.ReadToEnd()
).StreamReader#.ReadToEnd()
prevents problems related to the breaking change in .NET 6.