10

这就是我们的场景:我们有巨大的加密文件,以千兆字节为顺序,如果我们将它们读取到最后,我们可以正确解密。当我们读取并检测文件中的某些标志时出现问题,然后我们停止读取并调用 reader.Close(),会发生 CryptographicException:“填充无效且无法删除”。被抛出。我有一个重现此行为的小型控制台应用程序,要对其进行测试,只需运行它,它将在您的 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;
        }

    }
}

在我们原来的类中,我们在 Dispose() 期间执行 reader.Close。我的问题是,检查 reader.EndOfStream 是否为假然后捕获 CryptographicException 是否有效?还是加密/解密方法有问题?也许我们错过了一些东西。

问候!

4

5 回答 5

7

在 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和字段中。.NET Framework 4.5 及更高版本_OutputBuffer没有这个问题。)

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();
            }
        }
    }
}
于 2014-02-27T14:53:24.907 回答
1

Close调用Dispose(true)哪个调用FlushFinalBlock哪个抛出异常,因为这不是真正的最后一个块。

您可以通过覆盖该Close方法来防止这种情况,使其不调用FlushFinalBlock

public class SilentCryptoStream : CryptoStream {
    public SilentCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode) :
        base(stream, transform, mode) {
    }

    public override void Close() {
        this.Dispose(false);
        GC.SuppressFinalize(this);
    }
}

(您还需要手动关闭底层流。)

检查 reader.EndOfStream 是否为假然后捕获 CryptographicException 是否有效

我认为没关系。

于 2014-02-12T02:35:34.950 回答
1

据我了解,当读取的最后一个字节不是有效的填充字节时会引发异常。当您有意提前关闭流时,最后读取的字节很可能被视为“无效填充”并引发异常。由于您是故意结束的,因此忽略异常应该是安全的。

于 2013-03-26T15:47:49.627 回答
0

可以关掉填充吗?

// aes.Padding = PaddingMode.PKCS7;
aes.Padding = PaddingMode.None;
于 2013-03-26T15:46:57.567 回答
0

我的解决方案是,在我的派生类中,将其添加到我的 Dispose(bool) 覆盖中:

    protected override void Dispose(bool disposing)
    {
        // CryptoStream.Dispose(bool) has a bug in read mode. If the reader doesn't read all the way to the end of the stream, it throws an exception while trying to
        // read the final block during Dispose(). We'll work around this here by moving to the end of the stream for them. This avoids the thrown exception and
        // allows everything to be cleaned up (disposed, wiped from memory, etc.) properly.
        if ((disposing) &&
            (CanRead) &&
            (m_TransformMode == CryptoStreamMode.Read))
        {
            const int BUFFER_SIZE = 32768;
            byte[] buffer = new byte[BUFFER_SIZE];

            while (Read(buffer, 0, BUFFER_SIZE) == BUFFER_SIZE)
            {
            }
        }

        base.Dispose(disposing);
        ...

通过确保始终将流读取到末尾,可以避免 CryptStream.Dispose 中的内部问题。当然,您需要根据您所阅读内容的性质来权衡它,以确保它不会产生负面影响。仅对已知有限长度的源使用它。

于 2015-07-06T14:22:18.360 回答