Can Array.Clear() be used to zero out sensitive byte arrays? #48697
-
Can Array.Clear() be used to zero out sensitive byte arrays (e.g. encryption keys)? If not, then how would you go about zeroing sensitive data in .NET/.NET Core? This question has been asked on StackOverflow before, but the answers aren't particularly helpful (e.g. ProtectedMemory is only available in .NET Framework and SecureString is not recommended). The 'official' answer doesn't seem to be documented. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
Currently, none of C#, F#, or VB.NET permit the compiler to drop memory writes that it knows no one can read (from a purely deterministic functional flow), so it doesn't have the problem of memset-0+free in C. Likewise, we don't permit the JIT to skip the writes in similar cases. We added CryptographicOperations.ZeroMemory to .NET Core 2.1 just so that callers who want to guarantee that the memory write happens have a way to visibly assert that. Like Windows So... Array.Clear works. Except for when it doesn't. If you are going to fill an array with secrets then you want to have it pinned during the entire time that it contains a secret so that GC compaction won't kick in and make a copy. byte[] passwordArray = Encoding.UTF8.GetBytes("password");
// break here, go to memory view, see where passwordArray's contents currently live. Stay there.
// do a lot of things which allocate small objects
Array.Clear(passwordArray);
// break here. Your memory view might not show zeros. Open another memory view, look at passwordArray, it's zero.
// what happened? GC compaction decided it would be more convenient of passwordArray lived somewhere else, so it copied it. A better version of this would be // Don't have passwords in System.String if you can help it. You can't clear them.
// If you cheat and clear them by pointer you might crash the runtime, especially in future versions.
// Demonstrative example only.
// Yay for weasel clauses?
string password = "password";
Encoding encoding = Encoding.UTF8;
byte[] passwordArray = new byte[encoding.GetMaxByteCount(password)];
fixed (byte* ignored = passwordArray)
{
int written = encoding.GetBytes(password, passwordArray);
Span<byte> passwordSpan = passwordArray.AsSpan(0, written);
// same as before
CryptograhicOperations.ZeroMemory(passwordSpan); // Or passwordSpan.Clear(). Whatever.
} This time there aren't any copies, so it clears as expected, because it was pinned. You can avoid using // Don't have passwords in System.String if you can help it. You can't clear them.
// If you cheat and clear them by pointer you might crash the runtime, especially in future versions.
// Demonstrative example only.
// Yay for weasel clauses?
string password = "password";
Encoding encoding = Encoding.UTF8;
byte[] passwordArray = GC.AllocateArray<byte>(encoding.GetMaxByteCount(password), pinned: true);
int written = encoding.GetBytes(password, passwordArray);
Span<byte> passwordSpan = passwordArray.AsSpan(0, written);
// same as before
CryptograhicOperations.ZeroMemory(passwordSpan); // Or passwordSpan.Clear(). Whatever.
} That's probably nicer than |
Beta Was this translation helpful? Give feedback.
Currently, none of C#, F#, or VB.NET permit the compiler to drop memory writes that it knows no one can read (from a purely deterministic functional flow), so it doesn't have the problem of memset-0+free in C.
Likewise, we don't permit the JIT to skip the writes in similar cases.
We added CryptographicOperations.ZeroMemory to .NET Core 2.1 just so that callers who want to guarantee that the memory write happens have a way to visibly assert that. Like Windows
SecureZeroMemory
we'll guarantee that no matter what compiler optimizations and runtime optimizations are permitted that we will forcibly clear the target memory.So... Array.Clear works. Except for when it doesn't. If you are going t…