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

这就是我们拥有的场景:我们有大量的加密文件,大约为千兆字节,如果我们将它们读到最后,我们就可以正确解密。 当我们正在读取并检测文件中的某个标志时出现问题,然后我们停止读取并调用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. 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.EndOfStream是否为false然后捕获CryptographicException是否有效? 或者加密/解密方法有问题? 也许我们错过了什么。

问候!

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

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

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

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

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

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

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

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

Close调用Dispose(true)调用抛出exception的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是否为false,然后捕获CryptographicException

我觉得没关系。

你能关掉填充物吗?

 // aes.Padding = PaddingMode.PKCS7; aes.Padding = PaddingMode.None; 

我的解决方案是,在我的派生类中,将它添加到我的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中的内部问题。 当然,你需要权衡这与你正在阅读的内容的性质,以确保它没有负面影响。 仅针对已知有限长度的源使用它。