C# CryptoStream迫使我将敏感数据泄漏到RAM中
以下是我的目标:C# CryptoStream迫使我将敏感数据泄漏到RAM中,c#,.net,encryption,garbage-collection,pci-dss,C#,.net,Encryption,Garbage Collection,Pci Dss,以下是我的目标: 将字节[]解密到固定的字节[]缓冲区中 我不希望纯文本存在于内存中的任何其他位置,在我控制的这个固定的字节[]之外 我怎样才能在C#中做到这一点 我天真地使用了加密流类。但是,需要我给它一个输出流。我必须。所以我继续,给了它一个记忆流 我使用内存调试窗口在内存中进行了一些嗅探。我相信我发现MemoryStream有一个CryptoStream产生的任何东西的副本(用于缓冲?)。因此,现在纯文本在我的固定的字节[]中,也在可以由CLR随机复制的内存的另一部分中,我无法控制 这是我
字节[]
解密到固定的字节[]
缓冲区中字节[]
之外加密流
类。但是,需要我给它一个输出流。我必须。所以我继续,给了它一个记忆流
我使用内存调试窗口在内存中进行了一些嗅探。我相信我发现MemoryStream
有一个CryptoStream
产生的任何东西的副本(用于缓冲?)。因此,现在纯文本在我的固定的字节[]
中,也在可以由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()
:
加密流
内部未固定字节[]
)加密流
内部未固定字节[]
)MemoryStream
字节[]
(multiBlockArray)尝试一次性转换为所有块(而不是通常的_OutputBuffer)。它将多块数组写入流。然后它会丢失对该数组的引用,甚至不会尝试对其进行清理。当然,它也没有被钉住
CryptoStream.FlushFinalBlock()
&CryptoStream.Dispose()
:
两者都将
数组。清除
输出缓冲区。这总比什么都不做要好,尽管OutputBuffer没有固定,因此它仍然可能泄漏明文数据。这可能是一个固执己见的答案,所以请相信它的价值:
您已经使用的MemoryStream
构造函数是对传入数组pinnedDst
进行操作的构造函数。它不会增长、重新分配等。该数组只需对其进行读写。由于它已经被固定,您可以确保它不会被GC移动
但是,C#中的所有流操作都是通过使用byte[]
缓冲区对流进行读写操作的。当您写入加密流
时,它将创建某种纯文本缓冲区,用于将数据复制到内存流
--
它可能只有几个字节长,也可能与整个src
数组一样大。这(大概)取决于它使用的密码的块长度和/或它的编写方式
如果您确实希望避免在GC操作期间将纯文本移动并在未清除的情况下循环到托管堆中的任何机会,那么您可能必须p/调用windows crypto API。然而,P/调用windows Crypto API需要不安全的代码块来获取指向数组的指针,这意味着您正在启用使您容易受到内存攻击的东西——在进程中启用未经检查的指针解引用
我不会因为你上面写的代码而失眠。我也不允许.net程序集中需要处理PCI-DSS作用域解密的不安全代码块。就像您已经在做的那样,使用字节数组而不是字符串,使用分配的缓冲区在MemoryStream
上进行操作,并在完成后清除缓冲区——您应该可以开始了。icryptTransform decryptor=symm.CreateDecryptor();
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);
if(!decryptor.CanTransferorMMultipleBlocks)
抛出新的InvalidOperationException();
//因为我们正在解密src.Length是块对齐的。
int writed=decryptor.TransformBlock(src,0,src.Length,pinnedDst,0);
字节[]lastBlock=decryptor.TransformFinalBlock(Array.Empty(),0,0);
Buffer.BlockCopy(lastBlock,0,pinnedDst,write,lastBlock.Length);
这使得除最后一个块之外的所有内容都只能写入固定内存——除非算法实现需要在内部使用托管缓冲区。如果您处于PaddingMode.None(或零),那么我相信TransformFinalBlock将返回空数组,因为它不需要执行depad延迟。.NET 5.0添加了ImportFromPem:
using var rsa = new RSACryptoServiceProvider();
rsa.ImportFromPem(privateKey);
我在类中没有看到任何类似于构造函数中提供的缓冲区副本的内容。如果它不是
内存流
,那么可能是解密程序
,或者加密流
本身?我已经添加了关于我用来回答这个问题的解密程序的信息。我想这有点像手动实现CryptoStream.Write()
,我希望避免这种情况,但由于我将所有这些集中在一个位置,我可以尝试一下。谢谢