【问题标题】:Can a CryptoStream leave the base Stream open?CryptoStream 可以让基本流保持打开状态吗?
【发布时间】:2013-11-13 05:40:26
【问题描述】:

我创建了一个MemoryStream,将其传递给CryptoStream 进行编写。我希望CryptoStream 加密,并让MemoryStream 对我开放,然后再读入其他内容。但是一旦CryptoStream 被处理,它也会处理MemoryStream

CryptoStream 可以让基地MemoryStream 以某种方式打开吗?

using (MemoryStream scratch = new MemoryStream())
{
    using (AesManaged aes = new AesManaged())
    {
        // <snip>
        // Set some aes parameters, including Key, IV, etc.
        // </snip>
        ICryptoTransform encryptor = aes.CreateEncryptor();
        using (CryptoStream myCryptoStream = new CryptoStream(scratch, encryptor, CryptoStreamMode.Write))
        {
            myCryptoStream.Write(someByteArray, 0, someByteArray.Length);
        }
    }
    // Here, I'm still within the MemoryStream block, so I expect
    // MemoryStream to still be usable.
    scratch.Position = 0;    // Throws ObjectDisposedException
    byte[] scratchBytes = new byte[scratch.Length];
    scratch.Read(scratchBytes,0,scratchBytes.Length);
    return Convert.ToBase64String(scratchBytes);
}

【问题讨论】:

  • 你为什么首先使用流?只需在输入字节上调用encryptor.TransformFinalBlock。流主要用于增量加密/解密,但在您同时拥有完整数据时则不然。

标签: c# stream objectdisposedexception


【解决方案1】:

从 .NET 4.7.2 开始,有第二个构造函数,其中添加了一个名为 leaveOpen 的 bool 参数。如果设置为 true,则CryptoStream 的 dispose 方法将不会在底层流上调用 dispose。

Additionally,另一个没有leaveOpen参数的构造函数只是将参数转发给leaveOpen设置为false的新构造函数。

MSDN
CryptoStream.Dispose(bool disposing)

【讨论】:

  • +1 出于某种奇怪的原因,我无法调用构造函数的这个重载,但是,我能够设置 _leaveOpen 标志 via Reflection 以达到相同的效果
  • @StuartLC 您是否正在编写 .NET Standard 2.0 库?附加构造函数不是 .NET Standard 2.0 的一部分,因为它会破坏与不存在构造函数的 .NET Framework 4.6.1 的向后兼容性。
  • @StuartLC 感谢您的提示!这是一个非常肮脏的解决方法,但由于我们不会很快迁移到 .Net Core 并且我们的软件保证可以在 4.7.2 及更高版本上运行,因此这是使其正常工作的唯一方法。我不明白为什么之前没有添加这个。有足够多的场景您实际上必须加密但继续使用流(因为 CryptoStream 不允许搜索)。
【解决方案2】:

作为第二种解决方案,您可以创建一个 WrapperStream 对象,该对象仅传递除 Dispose / Close 之外的每个调用。围绕您的内存流制作一个包装器,将包装器交给加密流,现在关闭加密流不会触及内存流。

【讨论】:

    【解决方案3】:

    您可以使用 using 语句,但不能使用。您将需要手动管理对象的处置,还需要调用FlushFinialBlock() 以确保在处理之前将所有数据写入底层流。

    一旦你完成了对流的处理,你就可以在最后的 finally 块中释放你等待的所有资源。

    MemoryStream scratch = null;
    AesManaged aes = null;
    CryptoStream myCryptoStream = null;
    try
    {
        scratch = new MemoryStream();
        aes = new AesManaged();
    
        // <snip>
        // Set some aes parameters, including Key, IV, etc.
        // </snip>
        ICryptoTransform encryptor = aes.CreateEncryptor();
        myCryptoStream = new CryptoStream(scratch, encryptor, CryptoStreamMode.Write);
        myCryptoStream.Write(someByteArray, 0, someByteArray.Length);
    
        //Flush the data out so it is fully written to the underlying stream.
        myCryptoStream.FlushFinalBlock();
    
        scratch.Position = 0; 
        byte[] scratchBytes = new byte[scratch.Length];
        scratch.Read(scratchBytes,0,scratchBytes.Length);
        return Convert.ToBase64String(scratchBytes);
    }
    finally
    {
        //Dispose all of the disposeable objects we created in reverse order.
    
        if(myCryptoStream != null)
            myCryptoStream.Dispose();
    
        if(aes != null)
            aes.Dispose();
    
        if(scratch != null)
            scratch.Dispose();
    }
    

    【讨论】:

    • 对于给我 -1 的人,我的回答你认为“没有用”来保证 -1 吗?如果我的帖子中有不正确的信息,请通知我,以便我更正或删除它。我猜代码编写内部try-finally 的方式是不必要的。我会删除它。我能想到的唯一另一件事是aesscratchmyCryptoStream 之前被处置。我也会改正的。
    • 如果你想编写一个方法,在加密后返回基本流,并在返回基本流之前处理 CryptoStream?如果你处理 CryptoStream 有关系吗?还是在完成后将 BaseStream 丢弃就可以了?
    • 被否决:未说明如何从使用 CryptoStream 的方法返回基本流
    • CryptoStream Dispose 方法仍将释放底层流。你仍然得到重复的处置。您只是将其推迟到 finally 块。正确答案在 .NET 4.7.2 版新构造函数重载的情况下。
    【解决方案4】:

    我的简单解决方案:

    class NotClosingCryptoStream : CryptoStream
    {
        public NotClosingCryptoStream( Stream stream, ICryptoTransform transform, CryptoStreamMode mode )
            : base( stream, transform, mode )
        {
        }
    
        protected override void Dispose( bool disposing )
        {
            if( !HasFlushedFinalBlock )
                FlushFinalBlock();
    
            base.Dispose( false );
        }
    }

    【讨论】:

    • 它有效,但我有一个问题:正如 MSDN 提到的 disposing Type: System.Boolean true to release both managed and unmanaged resources; false to release only unmanaged resources。但据我所知,Stream 对象是非托管对象,那么这个解决方案如何防止 CryptoStream 不释放底层流?
    【解决方案5】:

    事实证明,没有任何必要将 using {} 块拆分为 try{}finally{} ...最终,您只需在 using 语句中使用 FlushFinalBlock() 并嵌套其他任何内容根据需要在里面。

    using (MemoryStream scratch = new MemoryStream())
    {
        using (AesManaged aes = new AesManaged())
        {
            // <snip>
            // Set some aes parameters, including Key, IV, etc.
            // </snip>
            ICryptoTransform encryptor = aes.CreateEncryptor();
            using (CryptoStream myCryptoStream = new CryptoStream(scratch, encryptor, CryptoStreamMode.Write))
            {
                myCryptoStream.Write(someByteArray, 0, someByteArray.Length);
                myCryptoStream.FlushFinalBlock();
                scratch.Flush();   // not sure if this is necessary
                byte[] scratchBytes = scratch.ToArray();
                return Convert.ToBase64String(scratchBytes);
            }
        }
    }
    

    【讨论】:

    • scrach.Flush() 确实是unnessasary,from the msdn "重写 Stream.Flush 方法,以便不执行任何操作。"
    猜你喜欢
    • 2012-01-25
    • 2020-08-23
    • 2010-12-15
    • 2014-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多