在EOF抛出异常之前停止解密:填充无效且无法删除

emm*_*uel 9 c# encryption aes encryption-symmetric

这就是我们拥有的场景:我们有大量的加密文件,大约为千兆字节,如果我们读到它们,我们可以正确解密.当我们读取并检测文件中的某个标志时会出现问题,然后我们停止读取并调用reader.Close(),会发生CryptographicException:"Padding无效且无法删除".被扔了.我有这个小控制台应用程序重现这种行为,测试它只是运行它,它将在你的C:\驱动器中创建一个文件然后按任意键时将逐行读取,并在按'q'时停止.

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

namespace encryptSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var transform = CreateCryptoTransform(true);
            // first create encrypted file
            using (FileStream destination = new FileStream("c:\\test_enc.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
            {
                using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
                {
                    using (StreamWriter source = new StreamWriter(cryptoStream))
                    {
                        for (int i = 0; i < 1000; i++)
                        {
                            source.WriteLine("This is just random text to fill the file and show what happens when I stop reading in the middle - " + i);
                        }
                        // Also tried this line, but is the same with or without it
                        cryptoStream.FlushFinalBlock();
                    }
                }
            }

            StreamReader reader;
            ICryptoTransform transformDec;
            CryptoStream cryptoStreamReader;

            transformDec = CreateCryptoTransform(false);
            FileStream fileStream = new FileStream("c:\\test_enc.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            cryptoStreamReader = new CryptoStream(fileStream, transformDec, CryptoStreamMode.Read);
            reader = new StreamReader(cryptoStreamReader);

            while (Console.In.ReadLine() != "q")
            {
                Console.WriteLine(reader.ReadLine());
            }

            try
            {
                cryptoStreamReader.Close();
                reader.Close();
                reader.Dispose();
            }
            catch (CryptographicException ex)
            {
                if (reader.EndOfStream)
                    throw;

            }
        }

        private static ICryptoTransform CreateCryptoTransform(bool encrypt)
        {
            byte[] salt = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Must be at least eight bytes.  MAKE THIS SALTIER!
            const int iterations = 1042; // Recommendation is >= 1000.
            const string password = "123456";

            AesManaged aes = new AesManaged();
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;
            // NB: Rfc2898DeriveBytes initialization and subsequent calls to   GetBytes   must be eactly the same, including order, on both the encryption and decryption sides.
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.IV = key.GetBytes(aes.BlockSize / 8);
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            ICryptoTransform transform = encrypt ? aes.CreateEncryptor(aes.Key, aes.IV) : aes.CreateDecryptor(aes.Key, aes.IV);
            return transform;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

在我们的原始类中,我们执行reader.Close在Dispose()期间.我的问题是,检查reader.EndOfStream是否为false然后捕获CryptographicException是否有效?或者加密/解密方法有问题?也许我们错过了什么.

问候!

Joe*_*nta 6

更新2:我不知道为什么我在以前的更新中认为链接代码在这里解决了主要问题.显然,如果FlushFinalBlock()抛出,则不会调用_stream.Close().有关我之前提到的错误更新,请参阅此答案的修订历史记录.

在Dispose(true)期间抛出此异常.从Dispose中抛出已经是一个设计缺陷(https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1065-do-not-raise-exceptions-in-unexpected-locations#dispose-methods),但更糟糕的是,因为即使在基础流关闭之前抛出此异常也是如此.

这意味着接收可能是CryptoStream 的Stream的任何东西都需要解决这个问题,并在"catch"块中关闭底层Stream本身(基本上需要引用完全不相关的东西),或者以某种方式警告所有侦听器可能仍然是开放的(例如,"不要试图删除基础文件 - 它仍然是开放的!").

不,在我的书中,这是一个非常大的疏忽,而其他答案似乎并没有解决根本问题.CryptoStream获取传入流的所有权,因此它需要在控制离开Dispose(true),故事结束之前关闭底层流.

理想情况下,它也应该永远不会抛弃那些并非真正例外的情况(例如"我们提前停止阅读,因为解密的数据格式错误,继续阅读是浪费时间").

我们的解决方案基本上是这样的(更新:但要注意 - 正如Will Krause在评论中指出的那样,这可能会将敏感信息留在私有_InputBuffer_OutputBuffer可通过反射访问的字段中.只需.NET Framework 4.5及更高版本没有这个问题.):

internal sealed class SilentCryptoStream : CryptoStream
{
    private readonly Stream underlyingStream;

    public SilentCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
        : base(stream, transform, mode)
    {
        // stream is already implicitly validated non-null in the base constructor.
        this.underlyingStream = stream;
    }

    protected override void Dispose(bool disposing)
    {
        try
        {
            base.Dispose(disposing);
        }
        catch (CryptographicException)
        {
            if (disposing)
            {
                this.underlyingStream.Dispose();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*ens 0

可以关闭填充吗?

// aes.Padding = PaddingMode.PKCS7;
aes.Padding = PaddingMode.None;
Run Code Online (Sandbox Code Playgroud)