Click here to Skip to main content
Click here to Skip to main content
Go to top

.NET Console Password Input By Masking Keyed-In Characters

, 27 Aug 2004
Rate this:
Please Sign up or sign in to vote.
Demonstrates how to intercept .NET console keyboard input and display a * in place of each actual typed-in character.

Sample Image - ConsolePasswordInput.jpg

Introduction

My colleague once asked me whether .NET provided any console password entry utility functions such that an application is able to receive and process characters typed in from the keyboard but not display them on the screen. Instead, a * character is to be output in place of each and every character typed in.

I could not recall any such utility in .NET but thought I could give a hand at developing one using Windows Console APIs imported to C# via DllImport attributes. A few days later, I managed to create one sample .NET console app with functions that performed exactly according to my colleague's specifications.

Summary Of Techniques Involved

You would need to import several Console APIs from Kernel32.DLL using the DllImportAttribute.

Structures and unions, matching those used in the unmanaged Kernel32 code must be defined and used in C#. The StructLayoutAttribute and its various values (e.g., LayoutKind) are important here. The FieldOffsetAttribute is also crucial in simulating unions in C#.

Also useful is the IntPtr struct which is defined in .NET and is used to represent a pointer or a handle in managed code.

I have also tried to make effective use of delegates to handle events.

Summary Of How The Code Works

I have put in a lot of comments into the C# code which should serve as documentation. However, several parts of the code, including the sequence of how we intercept and redisplay keyboard characters, require greater explanation.

These are summarized below:

The ConsolePasswordInput class

The main logic of the keyboard hooking code is encapsulated in the ConsolePasswordInput class. This class uses several Win32 Console APIs which are imported via DllImport.

The key Console API is ReadConsoleInput(). This is explained in more detail below. Much of the member data and functions of the ConsolePasswordInput class is based on the functional logic of this API.

I will explore deeper into the ReadConsoleInput() API and its associated structures next, and will resume discussion of the ConsolePasswordInput class after that.

The ReadConsoleInput() API and the INPUT_RECORD structure

We essentially want to intercept console keyboard input. We do this by using the Win32 Console API ReadConsoleInput(). This method blocks until a console input event occurs. A console event could be a keyboard event, a mouse event, a window buffer size changed event, a menu event, or a console window focus event (see MSDN documentation for ReadConsoleInput() for more details).

Prior to calling ReadConsoleInput(), we must define an array of INPUT_RECORD structures which will be filled by the ReadConsoleInput() function on return.

Each INPUT_RECORD structure contains a union of KEY_EVENT_RECORD, MOUSE_EVENT, WINDOW_BUFFER_SIZE_EVENT, MENU_EVENT, and FOCUS_EVENT structures, each being relevant to the possible types of events that can occur.

The INPUT_RECORD structure and its union are defined in C# as follows:

// The EventUnion struct is also treated as a union in the unmanaged world.
// We therefore use the StructLayoutAttribute and the FieldOffsetAttribute.
[StructLayout(LayoutKind.Explicit)]
internal struct EventUnion
{
 [FieldOffset(0)] internal KEY_EVENT_RECORD KeyEvent;
 [FieldOffset(0)] internal MOUSE_EVENT_RECORD MouseEvent;
 [FieldOffset(0)] internal WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
 [FieldOffset(0)] internal MENU_EVENT_RECORD MenuEvent;
 [FieldOffset(0)] internal FOCUS_EVENT_RECORD FocusEvent;
}
// The INPUT_RECORD structure is used within our application 
// to capture console input data.
internal struct INPUT_RECORD
{
 internal ushort EventType;
 internal EventUnion Event;
}

The INPUT_RECORD structure is defined as a normal C# structure. The Event union is of type EventUnion which is defined to be a normal struct but is specified with the "StructLayoutAttribute" and the "LayoutKind.Explicit" values to indicate that every field of the EventUnion struct is marked with a byte offset.

This byte offset is specified by the "FieldOffsetAttribute" and it indicates the number of bytes between the beginning of the struct in memory and the beginning of the field.

As you can see in the EventUnion struct, the fields KeyEvent, MouseEvent, WindowBufferSizeEvent, MenuEvent, and FocusEvent have been marked as being of offset 0. This is the only way that an unmanaged C/C++ union can be represented in C#.

The Code Lookup Hashtable and the ConsoleInputEvent Delegate

We now resume discussion of the ConsolePasswordInput class. This class defines a hashtable (htCodeLookup) which is used to map a console event type to its event handler.

We defined a delegate ConsoleInputEvent which is declared as follows:

// Declare a delegate to encapsulate a console event handler function.
// All event handler functions must return a boolean value indicating whether
// the password processing function should continue to read in another console
// input record (via ReadConsoleInput() API). 
// Returning a true indicates continue.
// Returning a false indicates don't continue.
internal delegate bool 
  ConsoleInputEvent(INPUT_RECORD input_record, ref string strBuildup);

As stated in the comments of the code above, the delegate encapsulates a console event handler function. Each console event handler function must take in an INPUT_RECORD structure and a reference to a string, and finally return a boolean value.

The idea of the event handler function is to process the console event (whatever type it is) and buildup the password string during the process. It is to return a true or a false value depending on whether the password string is deemed to be completely constructed (more on this later).

The code lookup hashtable is initialized during the construction of the ConsoleInputEvent() class:

// Public constructor.
// Here, we prepare our hashtable of console input event handler functions.
public ConsolePasswordInput()
{
  htCodeLookup = new Hashtable();
  // Note well that we must cast Constant.* event numbers to ushort's.
  // This is because Constants.*_EVENT have been declared as of type int.
  // We could have, of course, declare Constants.*_EVENT to be of type ushort
  // but I deliberately declared them as ints to show the importance of 
  // types in C#.
  htCodeLookup.Add((object)((ushort)(Constants.KEY_EVENT)), 
                      new ConsoleInputEvent(KeyEventProc));
  htCodeLookup.Add((object)((ushort)(Constants.MOUSE_EVENT)), 
                      new ConsoleInputEvent(MouseEventProc));
  htCodeLookup.Add((object)((ushort)(Constants.WINDOW_BUFFER_SIZE_EVENT)), 
                      new ConsoleInputEvent(WindowBufferSizeEventProc));
  htCodeLookup.Add((object)((ushort)(Constants.MENU_EVENT)), 
                      new ConsoleInputEvent(MenuEventProc));
  htCodeLookup.Add((object)((ushort)(Constants.FOCUS_EVENT)), 
                      new ConsoleInputEvent(FocusEventProc));
}

Note that I have defined handlers for all five types of console events, but only the KeyEventProc() function is non-trivial. This is because we are only interested in building up the password string during keyboard input. We can, of course, perform password processing during the other events (if deemed relevant and useful).

We shall next examine the PasswordInput() function and show how it uses the various event handlers to build up its password string.

The PasswordInput() Function and the KeyEventProc() Function

The PasswordInput() function is the main public function of the ConsolePasswordInput class. It first initializes the various console handles of the class (hStdin, hStdout) if they have not been initialized already, and temporarily sets the console mode to enable mouse and window console input events.

The while loop inside this function is the main driving force behind the keyboard character hooking:

// Main loop to collect characters typed into the console.
while (bContinueLoop == true)
{
  if
  (
    ReadConsoleInput
    (
      hStdin, // input buffer handle
      irInBuf, // buffer to read into
      128, // size of read buffer
      out cNumRead // number of records read
    ) == true
  )
  {
    // Dispatch the events to the appropriate handler.
    for (uint i = 0; i < cNumRead; i++)
    {
      // Lookup the hashtable for the appropriate
      // handler function... courtesy of Derek Kiong !
      ConsoleInputEvent cie_handler = 
        (ConsoleInputEvent)htCodeLookup[(object)(irInBuf[i].EventType)];
      // Note well that htCodeLookup may not have
      // the handler for the current event, 
      // so check first for a null value in cie_handler.
      if (cie_handler != null)
      {
        // Invoke the handler.
        bContinueLoop = cie_handler(irInBuf[i], ref refPasswordToBuild);
      }
    }
  }
}

Here, the ReadConsoleInput() function is called in a while loop. During each loop, the ReadConsoleInput() function is called. This function blocks until a Console Input Event occurs.

When such a console input event does occur, we lookup the htCodeLookup hashtable and determine the ConsoleInputEvent delegate associated with the event type. If we can find a delegate, we invoke it.

In our case, the only delegate we are interested in is the delegate for KEY_EVENT. This is why the delegates for the other events trivially return true which indicates to the while loop to continue its looping.

Let's examine the KeyEventProc() function:

// Event handler to handle a keyboard event. 
// We use this function to accumulate characters typed into the console and build
// up the password this way.
// All event handler functions must return a boolean value indicating whether
// the password processing function should continue to read in another console
// input record (via ReadConsoleInput() API). 
// Returning a true indicates continue.
// Returning a false indicates don't continue.
private bool KeyEventProc(INPUT_RECORD input_record, ref string strBuildup)
{
  // From the INPUT_RECORD, extract the KEY_EVENT_RECORD structure.
  KEY_EVENT_RECORD ker = input_record.Event.KeyEvent;
  // We process only during the keydown event.
  if (ker.bKeyDown != 0)
  {
    // This is to simulate a NULL handle value.
    IntPtr intptr = new IntPtr(0);
    // Get the current character pressed.
    char ch = (char)(ker.uchar.UnicodeChar);
    uint dwNumberOfCharsWritten = 0;
    // The character string that will be displayed
    // on the console screen.
    string strOutput = "*";
    // If we have received a Carriage Return character, we exit.
    if (ch == (char)'\r')
    {
      return false;
    }
    else
    {
      if (ch > 0)
      // The typed in key must represent a character
      // and must not be a control ley (e.g. SHIFT, ALT, CTRL, etc)
      {
        // A regular (non Carriage-Return character) is typed in...
        // We first display a '*' on the screen...
        WriteConsole
        (
          hStdout, // handle to screen buffer
          strOutput, // write buffer
          1, // number of characters to write
          ref dwNumberOfCharsWritten, // number of characters written
          intptr // reserved
        );
        // We build up our password string...
        string strConcat = new string(ch, 1);
        // by appending each typed in character at the end of strBuildup.
        strBuildup += strConcat;
        if (++iCounter < MaxNumberOfCharacters)
        {
          // Adding 1 to iCounter still makes iCounter
          // less than MaxNumberOfCharacters.
          // This means that the total number of characters
          // collected so far (this is 
          // equal to iCounter, by the way)
          // is less than MaxNumberOfCharacters.
          // We can carry on.
          return true;
        }
        else
        {
          // If, by adding 1 to iCounter makes iCounter
          // greater than MaxNumberOfCharacters,
          // it means that we have already collected
          // MaxNumberOfCharacters number of characters
          // inside strBuildup. We must exit now.
          return false;
        }
      }
    }
  }
  // The keydown state is false, we allow further characters to be typed in...
  return true;
}

As the KeyEventProc() function is called repeatedly by the PasswordInput() function's while loop, it accumulates characters typed into the console and builds up the password this way.

There are also two important conditions to note while processing the KEY_EVENT: whether a key was being pressed (as opposed to a key being released), and whether a control key (SHIFT, CTRL, or ALT) is the only key being pressed.

We cater to the first condition with the following "if" statement:

 if (ker.bKeyDown != 0)...

near the beginning of the function. The KEY_EVENT_RECORD.bKeyDown field indicates this. Note that the ReadConsoleInput() function will return when a key is pressed, and will return also when a key is released (even when the key is the same key that was first pressed). This is an expected specification of the ReadConsoleInput() function and is of no surprise. To prevent repeated processing of the same key, we perform action only when a key is being pressed and ignore the case where that key is being released.

We take care of the second condition with the following "if" statement:

if (ch > 0) ...

just before the WriteConsole() function call. Here "ch" is of char type, and it contains a copy of the value in KEY_EVENT_RECORD.uchar.UnicodeChar.

Note well another important point: the ReadConsoleInput() function will return when a control key is pressed. In the event that a non-control key is being pressed, the value in KEY_EVENT_RECORD.uchar.UnicodeChar will be the Unicode number of the character being pressed.

A non-control key can, of course, be pressed together with a control key. If so, KEY_EVENT_RECORD.uchar.UnicodeChar will contain a valid Unicode character and the KEY_EVENT_RECORD.dwControlKeyState field will contain the appropriate value indicating the appropriate state of the control keys (refer to the MSDN documentation for the KEY_EVENT_RECORD struct for more details).

This is important as it ensures that we are able to input uppercase and lower case characters in our password.

However, in the event that only a control key is being pressed, KEY_EVENT_RECORD.uchar.UnicodeChar will be zero. This is why we check whether "ch" is non-zero.

Because keyboard input is intercepted by the ReadConsoleInput() function, the character typed in is not displayed by the console by default. Our KeyEventProc() function takes this opportunity to display a '*' character to the console screen via the WriteConsole() function.

When the condition is right and the password is deemed completely constructed (this will be when the carriage-return key is pressed, or when the maximum number of characters allowed for a password is reached), this function will return a false to indicate to the PasswordInput() function to stop its while loop.

The Main() Function

A Main() function is included in the ConsolePasswordInput class. This function serves as an example of how we can make use of the ConsolePasswordInput class.

We first instantiate an object of this class:

ConsolePasswordInput cpi = new ConsolePasswordInput();

We then enter a while loop where the ConsolePasswordInput object's PasswordInput() function is called repeatedly to obtain a password from the user:

// Get an instance of the ConsolePasswordInput class to retrieve a password 
// of maximum size 5 characters.
while (iTries < 3)
{
  iTries++;
  strPassword = "";
  System.Console.Write ("Please enter your password : ");
  cpi.PasswordInput(ref strPassword, 20);
  System.Console.WriteLine();
  System.Console.WriteLine("Typed in password : {0}", strPassword);

  if (strPassword == "CodeProject")
  {
    System.Console.WriteLine ("Correct !");
    break;
  }
  else
  {
    if (iTries < 3)
    {
      System.Console.WriteLine ("try again...");
    }
    else
    {
      System.Console.WriteLine ("Wasted...");
    }
  }
}

We do this until either the user has tried a maximum of three times or when the password is correct (our ultra simple example here uses "CodeProject" as the password string).

Notice that we are able to use the usual System.Console line output functions (Write() and WriteLine()). Our keyboard hooking functions in the ConsolePasswordInput class do not interfere with this.

In Conclusion

The source code for this article contains the full Visual Studio .NET Solution Project files. It is an application by default, but can be easily changed to a library instead.

Besides showing how to intercept console keyboard input, I have also tried to show how unions can be constructed in C#. I have also tried to show effective use of delegates and hashtables.

I certainly hope this article can be of good use to other developers.

Best Regards,

Bio.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Lim Bio Liong
Web Developer
Singapore Singapore
Lim Bio Liong is a Specialist at a leading Software House in Singapore.
 
Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.
 
Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area.

Comments and Discussions

 
GeneralUse com object is easier Pinmembermclz52222-Feb-06 9:32 
QuestionBackspace does not delete output *s, it is intentional? PinmemberErhhung17-Feb-06 9:15 
GeneralCool! Pinmemberbneacetp12-Mar-05 17:50 
GeneralRe: Cool! PinmemberLim Bio Liong12-Mar-05 21:06 
GeneralRe: Cool! PinmemberMerlinblack2-Nov-05 13:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140921.1 | Last Updated 28 Aug 2004
Article Copyright 2004 by Lim Bio Liong
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid