简体   繁体   中英

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

There seems to be an encoding / stream handling mistake. You are writing ciphertext as if it was Unicode. I would recommend wrapping the ciphertext in a MemoryStream , then use an input CryptoStream ie one configured for reading, and then a StreamReader to read the text back. Now you've fully reversed the streams.

You need to encode the plain text in the Encrypt method and .Write the encoding result rather than the plain text. That is, the input byte array that the ICryptoTransform takes to process and returns the encrypted output byte array.

I'd suggest to get rid of the StreamWriter and write directly to the CryptoStream .

Here's a suggested fix.

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

... and in some caller:

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

See also the generic SymmetricCrypto<T> class here .

Imo the easiest way is to follow the MS pattern, eg 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() (ie 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM