Note: phiên bản Tiếng Việt của bài này ở link dưới.
https://duongnt.com/securestring-vie
A lot has been written about how to store passwords. But an often forgotten question is how to securely read passwords from user input and store them in memory. In old .NET Framework code projects, we sometimes encounter the SecureString type. This is one way the .NET team tried to keep the secret confidential.
Why we shouldn’t store a secret as a String object?
Below is a simple program to read user input and store it into a string
object. It seems simple enough.
private static void ReadString()
{
Console.Write("Please enter your password: ");
var password = Console.ReadLine();
Console.WriteLine();
}
However, when we use a string
object to store a secret, we can’t control its memory lifetime. As we know, in C# .NET a string
is a reference type, which means its memory is allocated on the heap, and it is subjected to garbage collection. We don’t know when (or even if) that memory will be reclaimed.
We will dump the memory of this program with dotnet-dump and try to recover the secret with WinDbg. This is the command to read all string
objects from the dump.
.foreach (address {!DumpHeap -type System.String -short }) {du ${address}+c }
When executed on our memory dump, it returns the following output.
What if we use a char array?
Maybe we can modify our code a little to read each key press separately and append them into a StringBuilder
object? This means there won’t be any string
to be recovered from memory, will there?
private static void ReadChars()
{
Console.Write("Please enter your password: ");
var sb = new StringBuilder();
ConsoleKeyInfo key;
do
{
key = Console.ReadKey();
if (key.Key != ConsoleKey.Enter)
{
sb.Append(key.KeyChar);
}
}
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
}
Unfortunately, in this case the char
array can still be recovered from the memory dump. First, we use this command to get the addresses of all char
arrays in the dump.
!DumpHeap -type System.Char -short
Then we can use this command to read the content of all those arrays.
db <address>
Although a little harder to read, we can still clearly see the secret.
Let’s switch to using SecureString
One important thing to keep in mind while using SecureString
is that we mustn’t store its contents in any temp string
object. After all, a temp string
still lives in memory and has a lifetime we can’t control. The code below is from Microsoft document. This is one way to read a secret from input and store it into a SecureString
.
private static void SecureString()
{
Console.Write("Please enter your password: ");
using var securePassword = new SecureString();
ConsoleKeyInfo key;
do
{
key = Console.ReadKey();
if (key.Key != ConsoleKey.Enter)
{
securePassword.AppendChar(key.KeyChar);
}
}
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
}
Let’s find the addresses of all char
arrays this time.
Next, let’s examine their contents.
As we can see, there is no char
array that stores our secret. But what about the SecureString
object itself? We will locate its address in the dump.
!DumpHeap -type System.Security.SecureString -short
This address also doesn’t store our secret.
Should we use SecureString in new projects?
Unfortunately, it is not recommended to use SecureString
in new projects. You can find the detailed reason in this link. Below are some takeaway points.
- While
SecureString
reduces the time we store the secret in plain text, it does not completely eliminate this risk. Because .NET still needs to get the secret from input somehow. SecureString
relies on encryption to keep the secret confidential. But this is not supported in all environments.
Instead, we should take advantage of certificates or Windows authentication…
Conclusion
I became interested in SecureString
after seeing it in an old repository. Turns out this is no longer the recommended solution to keep secrets confidential. But I still had fun playing with its memory dump.
One Thought on “Use SecureString to store secrets in C#”