I'm working on a simple command-line app, and have the need to collect a username and password. I don't want the password to be printed on the screen as they type, but System.Console.ReadLine doesn't seem to have any option to mask the input before it's echoed back to the console.
There are a couple ways I found to resolve this, the easy way, and the harder way with better UI.
Easy way:
-
ConsoleColor oldFore = Console.ForegroundColor;
-
Console.ForegroundColor = Console.BackgroundColor;
-
string password = Console.ReadLine();
-
Console.ForegroundColor = oldFore;
This basically hides the echoed input by setting the text color to be the same as the background color. This is a little weird because your users can't see any indication that they've entered any information.
The following function catches the input, and then echoes "*" instead. There's some rudimentary handling of backspace, but no other special keys (end, delete, arrow keys, etc) are handled properly. For now it's good enough for my use:
-
public static string ReadPassword() {
-
//keep reading
-
for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true)) {
-
if (cki.Key == ConsoleKey.Backspace) {
-
//rollback the cursor and write a space so it looks backspaced to the user
-
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
-
Console.Write(" ");
-
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
-
passbits.Pop();
-
}
-
else {
-
Console.Write("*");
-
passbits.Push(cki.KeyChar.ToString());
-
}
-
}
-
string[] pass = passbits.ToArray();
-
Array.Reverse(pass);
-
return string.Join(string.Empty, pass);
-
}
A bit messy and bug-ridden, but for now it's good enough for my purposes. When C# 3 comes out, I might be able to add things like this to the Console class using extension methods. Maybe a community-driven FrameworkPlus library will get some momentum, and we can all ditch our home-grown Utils libraries and reap the benefits of each other's work.
shirlz | 15-Sep-08 at 1:47 am | Permalink
Hey thanks heaps for that.. works like a charm!
Bill | 22-Oct-08 at 6:50 pm | Permalink
I would only make one real suggestion and that is to treat it like Console.IO.ReadLine and thus add this line of code just before your return statement:
Console.Out.Write(Console.Out.NewLine);
Will Buttitta | 07-Dec-08 at 1:37 pm | Permalink
One other adjustment…The block that handles the backspace should be wrapped in a condition that checks whether or not the stack has any password characters (i.e. if (passbits.Count > 0) {…), otherwise an empty stack exception is thrown if the user hits the backspace key one too many times.
Matt | 23-Apr-09 at 9:33 am | Permalink
Brilliant starting point; thanks very much. This really helped. The code is simple to follow. I managed to extend to check the cursor position wasn’t already at the start of the line if the Backspace key was pressed and only accept upper/lower case letters or numbers:
public static string ReadPassword()
{
Stack passChars = new Stack();
// Keep reading user entry:
for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
{
// If Backspace is pressed:
if (cki.Key == ConsoleKey.Backspace)
{
// Check cursor position is not already at 0 (start of line):
// Rollback the cursor and write a space so it looks backspaced to the user
if (Console.CursorLeft != 0)
{
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
Console.Write(” “);
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
passChars.Pop();
}
else
{
Console.Write(” “);
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
}
}
// Only accept keys; 0-9, A-Z and a-z
else
{
if ((cki.KeyChar >= 48 && cki.KeyChar = 65 && cki.KeyChar = 97 && cki.KeyChar <= 122))
{
Console.Write(“*”);
passChars.Push(cki.KeyChar.ToString());
}
else
{
Console.Write(” “);
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
}
}
}
string[] pass = passChars.ToArray();
Array.Reverse(pass);
return string.Join(string.Empty, pass);
}
tjrobinson | 31-Jul-09 at 5:18 am | Permalink
That works pretty well, thanks.
I’ve also found this which looks like it may be a more secure alternative:
http://derekslager.com/blog/posts/2006/10/reading-masked-input-from-the-system-console.ashx
Casey Morton | 26-Jan-10 at 12:26 pm | Permalink
Thanks for the code snippet. I made a couple of improvements on it though. First it would be more efficient to use a queue to store the characters. That way you save a reverse call at the end. Also, on the code posted by matt, it would be better to check the size of the stack/queue than check the position of the cursor. Checking the position of the cursor constrains you to only using this when the cursor is at the beginning of a new line. If you check the size of the stack/queue you can use it to do same-line prompts as well.
Casey Morton | 26-Jan-10 at 12:28 pm | Permalink
For anyone interested, here is my improved code:
public static string MaskedReadLine()
{
Queue passbits = new Queue();
for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
{
if (cki.Key == ConsoleKey.Backspace)
{
if (passbits.Count > 0)
{
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
Console.Write(” “);
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
passbits.Dequeue();
}
}
else
{
Console.Write(“*”);
passbits.Enqueue(cki.KeyChar.ToString());
}
}
Console.WriteLine();
string[] pass = passbits.ToArray();
return string.Join(string.Empty, pass);
}
Jerry Chen | 07-Apr-10 at 6:27 am | Permalink
It’s cool, thanks.
Adam Tappis | 17-Sep-10 at 4:52 pm | Permalink
Queue.ToArray() returns and an object[] so the above example fails. Why not just use a StringBuilder. Here’s my version which hadles both regular and masked input:
internal static string GetConsoleInput(string prompt, bool hide)
{
StringBuilder input = new StringBuilder();
Console.Write(prompt);
for (ConsoleKeyInfo keyinfo = Console.ReadKey(true); keyinfo.Key != ConsoleKey.Enter; keyinfo = Console.ReadKey(true))
{
//handle backspace
if (keyinfo.Key == ConsoleKey.Backspace)
{
if (input.Length > 0)
{
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
Console.Write(” “);
Console.SetCursorPosition(Console.CursorLeft – 1, Console.CursorTop);
input.Length -= 1;
}
}
else
{
Console.Write(hide ? ‘*’ : keyinfo.KeyChar);
input.Append(keyinfo.KeyChar);
}
}
Console.WriteLine();
return input.ToString();
}
Anurag | 13-Apr-11 at 11:38 pm | Permalink
Thanks, man . Exactly what i wanted, on the first page of the blog.. .
Florin C | 07-Aug-11 at 6:35 pm | Permalink
Good job,
Thanks to all here.
rnaga | 07-Feb-12 at 2:44 pm | Permalink
There is one another implementation found at
http://cinchoo.wordpress.com/2012/02/07/cinchoo-read-password-from-console/