的CryptoStream迫使我敏感数据泄漏到RAM

问题描述:

因此,这里有我的目标:的CryptoStream迫使我敏感数据泄漏到RAM

  1. 解密byte[]固定byte[]缓冲区。
  2. 我不希望明文存在于我所控制的这个固定的byte[]之外的其他地方。

如何在C#中执行此操作?

我天真地使用了CryptoStream类。但那要求我给它一个输出流。我必须。所以我继续给它一个MemoryStream

我在内存中做了一些嗅探,使用内存调试窗口。我相信我发现MemoryStream有一个来自CryptoStream的副本(用于缓冲?)。所以现在明文是在我固定的byte[]中,并且在这个可以被CLR随机复制并且我无法控制的存储器的其他部分中。

这是我使用的代码。为简单起见,我假设这里没有并发:

public class ExampleCode 
{ 
    private SymmetricAlgorithm algorithm; 
    private ICryptoTransform decryptor; 

    public ExampleCode(byte[] key, byte[] iv) // c'tor 
    { 
     algorithm = new RijndaelManaged(); 
     algorithm.Key = key; 
     algorithm.IV = iv; 
     decryptor = algorithm.CreateDecryptor(); 
    } 

    private long DecryptToPinnedArray(byte[] src, byte[] pinnedDst) 
    { 
     long bytesWritten = 0; 

     using (var ms = new MemoryStream(pinnedDst, writable: true)) 
     using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write)) 
     { 
      cs.Write(src, 0, src.Length); 
      cs.FlushFinalBlock(); 
      ms.Flush(); 

      bytesWritten = ms.Position; 
      return bytesWritten; 
     } 
    } 
} 

如何防止创建敏感数据的第二个副本? 我应该忘记CryptoStream并使用更低级的东西吗?对于这样的问题是否有最佳做法?

编辑:偷窥与反射器,这是我认为正在发生的事情:

CryptoStream.Write()

  1. cipher_data = - 复制 - =>_InputBuffer(一CryptoStream内部未固定的byte[])。
  2. _InputBuffer - =变换 - =>_OutputBuffer(一个CryptoStream内部取消固定byte[])。
  3. _OutputBuffer - =写 - =>MemoryStream

如果需要(并且可以)一次变换多于一个的块时,它会使用一个临时本地创建的更大的去钉扎byte[]multiBlockArray )尝试一次性转换所有块(而不是转入通常的_OutputBuffer)。它将multiBlockArray写入流中。然后它失去了对这个数组的引用,甚至没有试图去消毒它。当然,它不是固定的。

CryptoStream.FlushFinalBlock() & CryptoStream.Dispose()

双方将Array.Clear_OutputBuffer。这总比没有好,尽管_OutputBuffer没有固定,所以它仍然可能泄漏明文数据。

+0

我在[MemoryStream](https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/io/memorystream.cs)类中看不到任何东西,它看起来像* copy *您在构造函数中提供的缓冲区。 –

+0

如果它不是'MemoryStream',那么它可能就是'decryptor'或者'CryptoStream'本身?我已经添加了我用于解决问题的'decryptor'的信息。 –

这可能是一个自以为是的答案,所以你要什么值得:

您正在使用的已经构造MemoryStream是在阵列pinnedDst传递的操作之一。它不会增长,重新分配数组,它只会读取和写入数组。既然它已经被固定,你可以确定它不会被GC移动。

但是,C#中的所有流操作都使用byte[]缓冲区来读取或写入流。当您写入CryptoStream时,它将创建一些纯文本缓冲区来将数据复制到您的文件,它可能只有几个字节长,或者它可能与整个src阵列一样大。这可能(假设)达到它使用的密码的块长度和/或其写入方式。

如果您确实想避免在GC操作期间移动纯文本并在未清除的情况下将其循环到托管堆,那么您可能必须调用Windows加密API。但是,调用Windows Crypto API将需要不安全的代码块来获取指向你的数组的指针,这意味着你正在启用的是让你易受内存攻击的东西 - 在你的进程中启用未检查的指针取消引用。

我不会因为上面写的代码而失眠。我不会允许需要处理PCI-DSS范围解密的.net程序集中的不安全代码块。就像你已经在做的那样,使用字节数组而不是字符串,在MemoryStream上使用你分配的缓冲区进行操作,当你完成时清除 - 你应该很好走。

ICryptoTransform decryptor = symm.CreateDecryptor(); 

if (!decryptor.CanTransformMultipleBlocks) 
    throw new InvalidOperationException(); 

// Since we're decrypting src.Length is block aligned. 
int written = decryptor.TransformBlock(src, 0, src.Length, pinnedDst, 0); 
byte[] lastBlock = decryptor.TransformFinalBlock(Array.Empty<byte>(), 0, 0); 
Buffer.BlockCopy(lastBlock, 0, pinnedDst, written, lastBlock.Length); 

这使得一切除了最后一个块只能写你的固定内存 - 除非该算法的实现需要在内部使用托管缓存。如果你在PaddingMode.None(或零),那么我相信TransformFinalBlock将返回空数组,因为它不需要执行depad holdback。

+0

我想这有点像手动实现'CryptoStream.Write()',我希望避免这种情况,但是因为我把所有这些集中在一个位置,所以我可以尝试一下。谢谢! –