的CryptoStream迫使我敏感数据泄漏到RAM
因此,这里有我的目标:的CryptoStream迫使我敏感数据泄漏到RAM
- 解密
byte[]
成固定byte[]
缓冲区。 - 我不希望明文存在于我所控制的这个固定的
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()
:
-
cipher_data = - 复制 - =>_InputBuffer(一
CryptoStream
内部未固定的byte[]
)。 -
_InputBuffer - =变换 - =>_OutputBuffer(一个
CryptoStream
内部取消固定byte[]
)。 -
_OutputBuffer - =写 - =>
MemoryStream
如果需要(并且可以)一次变换多于一个的块时,它会使用一个临时本地创建的更大的去钉扎byte[]
(multiBlockArray )尝试一次性转换所有块(而不是转入通常的_OutputBuffer)。它将multiBlockArray写入流中。然后它失去了对这个数组的引用,甚至没有试图去消毒它。当然,它不是固定的。
CryptoStream.FlushFinalBlock()
& CryptoStream.Dispose()
:
双方将Array.Clear
的_OutputBuffer。这总比没有好,尽管_OutputBuffer没有固定,所以它仍然可能泄漏明文数据。
这可能是一个自以为是的答案,所以你要什么值得:
您正在使用的已经构造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。
我想这有点像手动实现'CryptoStream.Write()',我希望避免这种情况,但是因为我把所有这些集中在一个位置,所以我可以尝试一下。谢谢! –
我在[MemoryStream](https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/io/memorystream.cs)类中看不到任何东西,它看起来像* copy *您在构造函数中提供的缓冲区。 –
如果它不是'MemoryStream',那么它可能就是'decryptor'或者'CryptoStream'本身?我已经添加了我用于解决问题的'decryptor'的信息。 –