So here are my goals:
- Decrypt a
byte[]
into a pinnedbyte[]
buffer. - I don't want the plain-text to exist anywhere else in memory, outside of this pinned
byte[]
, which I control.
How can I do this in C#?
I naively used the CryptoStream
class. But that requires me to give it an output stream. I must. So I went ahead and gave it a MemoryStream
.
I did some sniffing around in memory, using the Memory debug window. I believe I found that MemoryStream
has a copy (for buffering?) of anything that comes out of the CryptoStream
. So now the plain-text is in both my pinned byte[]
, and in this other part of memory that can be randomly copied around by the CLR, and over which I have no control.
Here is the code I used. I assume no concurrency here for simplicity:
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;
}
}
}
How do I prevent this second copy of the sensitive data from ever being created?
Should I forget about CryptoStream
and use something more low-level? Is there a best-practice for issues like this?
EDIT: Peeking in with a reflector, this is what I think is happening:
CryptoStream.Write()
:
- cipher_data -=copy-=> _InputBuffer (a
CryptoStream
internal unpinnedbyte[]
). - _InputBuffer -=transform-=> _OutputBuffer (a
CryptoStream
internal unpinnedbyte[]
). - _OutputBuffer -=write-=>
MemoryStream
If it needs to (and can) transform more than one block at a time, it will use a temporary locally created larger unpinned byte[]
(multiBlockArray) to try and transform into it all the blocks in one go (instead of into the usual _OutputBuffer). It writes multiBlockArray to the stream. It then loses the reference to this array, and doesn't even attempt to sanitize it. Of course, it's not pinned anyway.
CryptoStream.FlushFinalBlock()
& CryptoStream.Dispose()
:
Both will Array.Clear
the _OutputBuffer. This is better than nothing, although _OutputBuffer is not pinned, so it can still potentially leak the plaintext data.