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:
[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;
}
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:
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 ConsolePasswordInput()
{
htCodeLookup = new Hashtable();
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:
while (bContinueLoop == true)
{
if
(
ReadConsoleInput
(
hStdin,
irInBuf,
128,
out cNumRead
) == true
)
{
for (uint i = 0; i < cNumRead; i++)
{
ConsoleInputEvent cie_handler =
(ConsoleInputEvent)htCodeLookup[(object)(irInBuf[i].EventType)];
if (cie_handler != null)
{
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:
private bool KeyEventProc(INPUT_RECORD input_record, ref string strBuildup)
{
KEY_EVENT_RECORD ker = input_record.Event.KeyEvent;
if (ker.bKeyDown != 0)
{
IntPtr intptr = new IntPtr(0);
char ch = (char)(ker.uchar.UnicodeChar);
uint dwNumberOfCharsWritten = 0;
string strOutput = "*";
if (ch == (char)'\r')
{
return false;
}
else
{
if (ch > 0)
{
WriteConsole
(
hStdout,
strOutput,
1,
ref dwNumberOfCharsWritten,
intptr
);
string strConcat = new string(ch, 1);
strBuildup += strConcat;
if (++iCounter < MaxNumberOfCharacters)
{
return true;
}
else
{
return false;
}
}
}
}
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:
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.