Securely Reading Passwords from the Console¶
If you've ever written a console application which requires the user to enter sensitive information like a password or a token, you might have wrestled with concerns of exposing the password in plain text within the console window.
I was writing a new console application earlier today after spending most of my time in PowerShell for the last three years, and I found myself wanting to use Read-Host -AsSecureString
, and remembered how much I take for granted that PowerShell gives us so much for free.
After making sure none of the Console.Read*
methods baked into .NET would give me what I wanted, I wrote a fairly short SecureConsole
class with a SecureConsole.ReadLine()
method along with a SecureConsole.GetCredential(string message)
method. I wanted to emulate PowerShell's Get-Credential
since I needed both a username and password.
Here's what I ended up with. The SecureConsole.ReadLine()
method will...
- read any non-control character entered by the user
- append each new
char
to aSystem.Security.SecureString
- write an asterisk (*) symbol back to the console
- accept the backspace key and behave as expected
Here's the SecureConsole
class, and a demo program where I'm calling SecureConsole.GetCredential()
to prompt the user for their credentials. The password will be recorded as a SecureString
and then paired with the username to create a System.Net.NetworkCredential
. For testing purposes, the plain text password from the credential is printed out to verify the text was received properly. Read on after the code sample for details.
The main loop¶
The program is a simple loop where we read from the console until the user enters a credential with a blank user name. Once a credential is entered, the password is printed to the screen and we do it again.
- This loop will continue forever, or until we reach the
break;
on line 17 by pressing enter without entering a username. - The
GetCredential()
method is called here without a message, and it returns aSystem.Net.NetworkCredential
object. - This is a just a sample, and for testing purposes we print the
Password
property of the network credential we received. This statement uses string interpolation. - We call
Console.ReadKey()
with the booleantrue
to indicate that the key should be suppressed from the console.
The GetCredential() methods¶
At the top of the SecureConsole
class are the GetCredential()
and an overload GetCredential(message)
which optionally prints the specified message to the user before presenting the "Username" and "Password" fields.
- In the first overload of the
GetCredential()
method, we call the second overload with an empty message. - In the second overload of
GetCredential()
, we print the message to the console if one was provided. - Then we collect the plain text username and a
System.Security.SecureString
password before returning the pair in a newSystem.Net.NetworkCredential
.
SecureConsole.ReadLine()¶
Finally, the SecureConsole.ReadLine()
method. It's similar to Console.ReadLine()
in behavior as accepts console input until a carriage return is received. The difference is that the character will not be written to the terminal, it gets stored one character at a time into an encrypted SecureString
, and an asterisk will be written to the console so the user recognizes that the character has been received.
- This
while
loop usesConsole.ReadKey(true)
to receive a keypress from the console, and as long as it isn't an Enter key, the loop executes. - Next we check to see if the key was the
ConsoleKey.Backspace
. If so, we write two\b
backspace characters to the terminal on either side of a "space" character. This effectively types "backspace - space - backspace" into the console to erase the last asterisk. - If the key wasn't a backspace, and the key is also not a control character like CTRL or HOME, then an asterisk is written to the console, and we append the character to the SecureString defined on line 54.
- We're now out of the
while
loop, but the last Enter keypress was suppressed from the console, so we useConsole.Write(Environment.NewLine)
to move the console cursor to the start of the next line before returning the completedSecureString
.
Final thoughts¶
There are probably more secure and complex ways to protect user input in a console app and thwart shoulder-surfing ne'er-do-wells, but this method seemed like a solid, lightweight alternative to showing passwords in plain text and storing them in simple strings. I wonder if there's a way to do it where we don't keep an unprotected char
in memory? Let me know if there's a simpler, and/or more secure method to accomplish the same thing within the scope of a console application!