- Tuesday, October 17, 2006
Reading Masked Input from the System Console with .NET 2.0
In the .NET Framework 2.0,
System
'sConsole
class was enhanced to provide a variety of new functionality. The ability to trap typed keys was introduced, as well as explicit control for cursor positioning. In a nutshell, managed developers were given just enough control over the system console to read masked input, which is what this post is about.The following sections contain the details of reading masked input (think passwords) from the console.
Choosing a String Format
Also introduced in the 2.0 version of the Framework was the
SecureString
class, which wraps a string buffer that is encrypted in memory and can be freed predictably. The downside is that it's a bit more complicated to use, and it is only usable in a small number of standard APIs (Process
.Start
being the most relevant). Still, it's the best choice for reading a password to be fed into one of the API methods which support it.It is also quite useful to obtain a normal
System
.String
instance for other use cases. If you're collecting a password to send to an FTP server, for example, it's quite useless in the context of overall system security to trouble yourself storing the password encrypted in memory (the password is sent in clear text via FTP's control connection).Rather than limit the API to one or the other, I decided to add support for both. The code which collects input from the console operates using the following interface abstraction:
private interface IBuffer { void InsertChar(int index, char c); void DeleteChar(int index); void Clear(); }
Concrete implementations of this interface are provided which wrap a
SecureString
instance as well as aStringBuilder
. Listed below is the implementation for the former:private class SecureStringBuffer : IBuffer { private SecureString buffer; public SecureStringBuffer(SecureString buffer) { this.buffer = buffer; } public void InsertChar(int index, char c) { this.buffer.InsertAt(index, c); } public void DeleteChar(int index) { this.buffer.RemoveAt(index); } public void Clear() { this.buffer.Clear(); } }
Reading Console Input
The only method used for receiving input from the console is
Console
.ReadKey
. An overload of this method enables the caller to trap input -- that is, read a character without it being automatically echoed to the console by the framework. When the user presses a character key, for example, we append it to the buffer (via ourIBuffer
abstraction) and emit the mask character (*).The input handling code is rather uninteresting in long form, but here's a snippet to give you a general idea (the remainder is available in the full source code, attached):
ConsoleKeyInfo keyInfo; while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) { if (keyInfo.Key == ConsoleKey.Backspace) { if (position > 0) { buffer.DeleteChar(--position); Write(startPosition + --length, ' '); } } else if ... }
I tried to emulate the basic behavior of the standard
ReadLine
method as much as possible -- that said, I'm sure I missed a few shortcuts which I don't happen to be aware of. If I missed something obvious, feel free to add a comment and I'll update the source.Using MaskedInputReader
Using the
MaskedInputReader
is very simple. Here's a sample invocation which reads aSecureString
(fromTest.cs
):Console.WriteLine("Enter current password: "); using (SecureString password = MaskedConsoleReader.ReadSecureLine()) { Process.Start("notepad.exe", Environment.UserName, password, Environment.UserDomainName); }
One of the primary motivations for building this library was to enable accepting credentials from MSBuild tasks. This is likely to be an increasingly common need with the release of Vista inspiring more developers to run LUA. A task to install an assembly to the GAC, for example, would require elevation if run by a LUA user.
Summary
The feature set is summarized below:
- Support for multiple string types.
- Backspace and delete handling.
- Line buffer navigation via arrow keys or Home / End.
The source code is attached to this post. It's MIT licensed, which means it can be used commercially provided the copyright stays in tact.
Attachments
Comments
- Saturday, February 09, 2008 12:17:11 AM by Bill
- Wednesday, September 24, 2008 2:48:40 PM by Davidreally handy. I'm gonna use it (personal use) for a small utility to unlock BitLocked volumes in W2k8
Thanks! - Friday, January 30, 2009 11:36:13 PM by Lars BrandtGreat! Thanks for the post.
- Friday, July 31, 2009 9:23:41 AM by tjrobinsonGreat code and explanation, thanks
- Friday, October 02, 2009 3:08:53 PM by Lukaszit doesn't work with Hi-ASCII nor DBCS because console is not utf8
- Monday, July 05, 2010 8:04:19 PM by NorthernerI was looking to mask a password in the Console in the easiest possible way, and I came up with this:
Console.WriteLine("Username:");
string username = Console.ReadLine();
Console.WriteLine("\nPassword:");
//ensure the password is not visible
ConsoleColor cul = Console.ForegroundColor;
Console.ForegroundColor = Console.BackgroundColor;
string password = Console.ReadLine();
Console.ForegroundColor = cul;
Changing the ForegroundColor to match the background ONLY affects the current console line. You dont see any characters because the text is the same color as the background. - Thursday, July 08, 2010 7:55:50 PM by RaitheThis is more Northerner. The issue with only changing the foreground/background color is that you can simply highlight the text and you can see it.
- Friday, April 29, 2011 6:22:30 PM by BrianYou saved me from writing this from scratch. Very useful. Thank you.
- Tuesday, February 07, 2012 6:46:26 PM by rnagaYou can find another implementation at
http://cinchoo.wordpress.com/2012/02/07/cinchoo-read-password-from-console/
The ReadKey method returns both character keystrokes (letters, numbers, etc.) and function keystrokes (function keys, cursor movement keys, etc.). Since function keys don't actually provide characters that could be part of a password, it would be best to filter them out, but unfortunately, the ConsoleKeyInfo structure does not provide a property to classify whether a keystroke is a character or function key. The documentation does say that the KeyChar property provides the character *if one is available*. It turns out that if one is not available, KeyChar is set to the "zero" value of a character - the same thing you'd get if you did "(char)0" or "new char()". I put in a test for that and it seems to be filtering appropriately.