Search code examples
c#encryption.net-3.5encryption-symmetric

Why is my Decrypt Method throwing a "Length of the data to decrypt is invalid" Cryptographic Exception


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();
        }
    }
}

Solution

  • 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:

    • Should you later Unicode-decode the decrypted data (which is likely), it is more efficient to return a string instead of a byte[] in Decrypt() (i.e. return reader.ReadToEnd()).
    • As for padding, PKCS7 padding should be used instead of zero padding, since PKCS#7 padding is more reliable (with respect to your comment in the code).
    • Using StreamReader#.ReadToEnd() prevents problems related to the breaking change in .NET 6.