Question

I'm puzzled by what appears to be a quirk of the .NET CryptoStream class: its Dispose() method reads past the end of the ciphertext looking for padding that it shouldn't, and throws a CryprographicException as a result.

The C# program below encrypts a few bytes, resizes the ciphertext array so that there are more (nonsense) bytes after the end of the ciphertext, and then attempts to decrypt it. The salient points are:

  • The ciphertext is 8 bytes, one 3DES cipher block. Since I only write 6 bytes into the CryptoStream and it's using PaddingMode.PKCS7 (the default), the remaining two bytes in the block are filled with the padding value 0x02.
  • The ciphertext array is subsequently resized to 16 bytes, two 3DES blocks. The second block is uninitialized nonsense, not valid cipher output.
  • When decrypting, I read exactly 6 bytes from the CryptoStream; I'm not asking it to decrypt into the nonsense portion, and I'm not relying on it recognizing the padding to figure out when it's reached the end of the plaintext.

The problem is that when the decrypting CryptoStream's Dispose() is called (automatically at the end of the using block), I get a CryptographicException with the message "Bad Data". Its stack trace shows that it was executing CryptoStream.FlushFinalBlock(), and all 16 bytes have been consumed from the ciphertextStream, not just the 8 corresponding to the actual encrypted data.

If I remove the line that resizes the ciphertext array, the program works correctly. And if I do tripleDes.Padding = PaddingMode.None before decrypting, the program also works correctly — but that basically makes the padding bytes part of the plaintext, so I'd rather not do that. Clearly, the problem is padding-related; as far as I can tell, it's decrypted that second block and is expecting to find valid PKCS7-style padding at the end of it.

Since I'm only reading enough from the CryptoStream to require one block to be decrypted, and that block is a correctly-padded final block, and then I close the CryptoStream without reading any more, why does the stream think it needs to read another block and look for more padding? Why is it even trying to consume more input as part of its Dispose()?


using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] plaintext = { 0, 1, 2, 3, 4 };

            using (SymmetricAlgorithm tripleDes = TripleDESCryptoServiceProvider.Create())
            {
                // Encrypt the plaintext
                byte[] ciphertext;
                using (MemoryStream ciphertextStream = new MemoryStream())
                {
                    using (ICryptoTransform encryptor = tripleDes.CreateEncryptor())
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(ciphertextStream, encryptor, CryptoStreamMode.Write))
                        {
                            cryptoStream.WriteByte((byte)plaintext.Length);
                            cryptoStream.Write(plaintext, 0, plaintext.Length);
                            cryptoStream.FlushFinalBlock();
                        }
                    }

                    ciphertext = ciphertextStream.ToArray();
                }

                // *** Add some non-ciphertext garbage to the end ***
                Array.Resize(ref ciphertext, ciphertext.Length + 8);

                // Now decrypt it again
                byte[] decryptedPlaintext;
                using (MemoryStream ciphertextStream = new MemoryStream(ciphertext, false))
                {
                    using (ICryptoTransform decryptor = tripleDes.CreateDecryptor())
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(ciphertextStream, decryptor, CryptoStreamMode.Read))
                        {
                            int length = cryptoStream.ReadByte();

                            decryptedPlaintext = new byte[length];

                            int i = 0;
                            while (i < length)
                            {
                                int bytesRead = cryptoStream.Read(decryptedPlaintext, i, (length - i));
                                if (bytesRead == 0) break;
                                else i += bytesRead;
                            }
                        }  // CryptographicException: "Bad Data"
                    }
                }

                System.Diagnostics.Debug.Assert(decryptedPlaintext.SequenceEqual(plaintext));
            }
        }
    }
}

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top