This is the scenario we have: We have huge encrypted files, in the order of gigabytes that we can decrypt correctly if we read them until the end. The problem arises when we ar
This exception is thrown during Dispose(true). Throwing from Dispose is already a design flaw (https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1065-do-not-raise-exceptions-in-unexpected-locations#dispose-methods), but it's even worse since this exception is thrown even before the underlying stream is closed.
This means that anything that receives a Stream that might be a CryptoStream needs to work around this and either close the underlying Stream themselves in a 'catch' block (essentially needing a reference to something completely unrelated), or somehow warn all listeners that the stream may still be open (e.g., "don't try to delete the underlying file -- it's still open!").
No, in my book, this is a pretty big oversight, and the other answers don't seem to address the fundamental issue. CryptoStream takes ownership of the passed-in stream, so it takes on the responsibility to close the underlying stream before control leaves Dispose(true), end of story.
Ideally, it should also never throw under circumstances that are not truly exceptional (such as "we stopped reading early, because the decrypted data is in the wrong format and it's a waste of time to continue reading").
Our solution was basically this (update: but be warned -- as Will Krause pointed out in the comments, this could leave sensitive information lying around in the private _InputBuffer
and _OutputBuffer
fields that can be accessed via reflection. Versions 4.5 and above of the .NET Framework don't have this problem.):
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();
}
}
}
}