Password masking console application

C#PasswordsConsole ApplicationUser InputMasking

C# Problem Overview


I tried the following code...

string pass = "";
Console.Write("Enter your password: ");
ConsoleKeyInfo key;

do
{
    key = Console.ReadKey(true);

    // Backspace Should Not Work
    if (key.Key != ConsoleKey.Backspace)
    {
        pass += key.KeyChar;
        Console.Write("*");
    }
    else
    {
        Console.Write("\b");
    }
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);

Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);

But this way the backspace functionality doesn't work while typing the password. Any suggestion?

C# Solutions


Solution 1 - C#

Console.Write("\b \b"); will delete the asterisk character from the screen, but you do not have any code within your else block that removes the previously entered character from your pass string variable.

Here's the relevant working code that should do what you require:

var pass = string.Empty;
ConsoleKey key;
do
{
    var keyInfo = Console.ReadKey(intercept: true);
    key = keyInfo.Key;

    if (key == ConsoleKey.Backspace && pass.Length > 0)
    {
        Console.Write("\b \b");
        pass = pass[0..^1];
    }
    else if (!char.IsControl(keyInfo.KeyChar))
    {
        Console.Write("*");
        pass += keyInfo.KeyChar;
    }
} while (key != ConsoleKey.Enter);

Solution 2 - C#

For this you should use the System.Security.SecureString

public SecureString GetPassword()
{
    var pwd = new SecureString();
    while (true)
    {
        ConsoleKeyInfo i = Console.ReadKey(true);
        if (i.Key == ConsoleKey.Enter)
        {
            break;
        }
        else if (i.Key == ConsoleKey.Backspace)
        {
            if (pwd.Length > 0)
            {
                pwd.RemoveAt(pwd.Length - 1);
                Console.Write("\b \b");
            }
        }
        else if (i.KeyChar != '\u0000' ) // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
        {
            pwd.AppendChar(i.KeyChar);
            Console.Write("*");
        }
    }
    return pwd;
}

Solution 3 - C#

Complete solution, vanilla C# .net 3.5+

Cut & Paste :)

	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;

	namespace ConsoleReadPasswords
	{
		class Program
		{
			static void Main(string[] args)
			{
				Console.Write("Password:");

				string password = Orb.App.Console.ReadPassword();

				Console.WriteLine("Sorry - I just can't keep a secret!");
				Console.WriteLine("Your password was:\n<Password>{0}</Password>", password);

				Console.ReadLine();
			}
		}
	}

	namespace Orb.App
	{
		/// <summary>
		/// Adds some nice help to the console. Static extension methods don't exist (probably for a good reason) so the next best thing is congruent naming.
		/// </summary>
		static public class Console
		{
			/// <summary>
			/// Like System.Console.ReadLine(), only with a mask.
			/// </summary>
			/// <param name="mask">a <c>char</c> representing your choice of console mask</param>
			/// <returns>the string the user typed in </returns>
			public static string ReadPassword(char mask)
			{
				const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
				int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const

				var pass = new Stack<char>();
				char chr = (char)0;

				while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
				{
					if (chr == BACKSP)
					{
						if (pass.Count > 0)
						{
							System.Console.Write("\b \b");
							pass.Pop();
						}
					}
					else if (chr == CTRLBACKSP)
					{
						while (pass.Count > 0)
						{
							System.Console.Write("\b \b");
							pass.Pop();
						}
					}
					else if (FILTERED.Count(x => chr == x) > 0) { }
					else
					{
						pass.Push((char)chr);
						System.Console.Write(mask);
					}
				}

				System.Console.WriteLine();

				return new string(pass.Reverse().ToArray());
			}

			/// <summary>
			/// Like System.Console.ReadLine(), only with a mask.
			/// </summary>
			/// <returns>the string the user typed in </returns>
			public static string ReadPassword()
			{
				return Orb.App.Console.ReadPassword('*');
			}
		}
	}

Solution 4 - C#

Taking the top answer, as well as the suggestions from its comments, and modifying it to use SecureString instead of String, test for all control keys, and not error or write an extra "*" to the screen when the password length is 0, my solution is:

public static SecureString getPasswordFromConsole(String displayMessage) {
	SecureString pass = new SecureString();
	Console.Write(displayMessage);
	ConsoleKeyInfo key;

	do {
		key = Console.ReadKey(true);

		// Backspace Should Not Work
		if (!char.IsControl(key.KeyChar)) {
			pass.AppendChar(key.KeyChar);
			Console.Write("*");
		} else {
			if (key.Key == ConsoleKey.Backspace && pass.Length > 0) {
				pass.RemoveAt(pass.Length - 1);
				Console.Write("\b \b");
			}
		}
	}
	// Stops Receving Keys Once Enter is Pressed
	while (key.Key != ConsoleKey.Enter);
	return pass;
}

Solution 5 - C#

Mine ignores control characters and handles line wrapping:

public static string ReadLineMasked(char mask = '*')
{
    var sb = new StringBuilder();
    ConsoleKeyInfo keyInfo;
    while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter)
    {
        if (!char.IsControl(keyInfo.KeyChar))
        {
            sb.Append(keyInfo.KeyChar);
            Console.Write(mask);
        }
        else if (keyInfo.Key == ConsoleKey.Backspace && sb.Length > 0)
        {
            sb.Remove(sb.Length - 1, 1);

            if (Console.CursorLeft == 0)
            {
                Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                Console.Write(' ');
                Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
            }
            else Console.Write("\b \b");
        }
    }
    Console.WriteLine();
    return sb.ToString();
}

Solution 6 - C#

This masks the password with a red square, then reverts back to the original colours once the password has been entered.

It doesn't stop the user from using copy/paste to get the password, but if it's more just about stopping someone looking over your shoulder, this is a good quick solution.

Console.Write("Password ");
ConsoleColor origBG = Console.BackgroundColor; // Store original values
ConsoleColor origFG = Console.ForegroundColor;

Console.BackgroundColor = ConsoleColor.Red; // Set the block colour (could be anything)
Console.ForegroundColor = ConsoleColor.Red;

string Password = Console.ReadLine(); // read the password

Console.BackgroundColor= origBG; // revert back to original
Console.ForegroundColor= origFG;

Solution 7 - C#

Reading console input is hard, you need to handle special keys like Ctrl, Alt, also cursor keys and Backspace/Delete. On some keyboard layouts, like Swedish Ctrl is even needed to enter keys that exist directly on US keyboard. I believe that trying to handle this using the "low-level" Console.ReadKey(true) is just very hard, so the easiest and most robust way is to just to disable "console input echo" during entering password using a bit of WINAPI.

The sample below is based on answer to Read a password from std::cin question.

    private enum StdHandle
    {
        Input = -10,
        Output = -11,
        Error = -12,
    }

    private enum ConsoleMode
    {
        ENABLE_ECHO_INPUT = 4
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(StdHandle nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out int lpMode);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetConsoleMode(IntPtr hConsoleHandle, int dwMode);

    public static string ReadPassword()
    {
        IntPtr stdInputHandle = GetStdHandle(StdHandle.Input);
        if (stdInputHandle == IntPtr.Zero)
        {
            throw new InvalidOperationException("No console input");
        }

        int previousConsoleMode;
        if (!GetConsoleMode(stdInputHandle , out previousConsoleMode))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not get console mode.");
        }

        // disable console input echo
        if (!SetConsoleMode(stdInputHandle , previousConsoleMode & ~(int)ConsoleMode.ENABLE_ECHO_INPUT))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not disable console input echo.");
        }

        // just read the password using standard Console.ReadLine()
        string password = Console.ReadLine();
        
        // reset console mode to previous
        if (!SetConsoleMode(stdInputHandle , previousConsoleMode))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not reset console mode.");
        }

        return password;
    }

Solution 8 - C#

I found a bug in shermy's vanilla C# 3.5 .NET solution which otherwise works a charm. I have also incorporated Damian Leszczyński - Vash's SecureString idea here but you can use an ordinary string if you prefer.

THE BUG: If you press backspace during the password prompt and the current length of the password is 0 then an asterisk is incorrectly inserted in the password mask. To fix this bug modify the following method.

    public static string ReadPassword(char mask)
    {
        const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
        int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const

        
        SecureString securePass = new SecureString();

        char chr = (char)0;

        while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
        {
            if (((chr == BACKSP) || (chr == CTRLBACKSP)) 
                && (securePass.Length > 0))
            {
                System.Console.Write("\b \b");
                securePass.RemoveAt(securePass.Length - 1);

            }
            // Don't append * when length is 0 and backspace is selected
            else if (((chr == BACKSP) || (chr == CTRLBACKSP)) && (securePass.Length == 0))
            {
            }

            // Don't append when a filtered char is detected
            else if (FILTERED.Count(x => chr == x) > 0)
            {
            }

            // Append and write * mask
            else
            {
                securePass.AppendChar(chr);
                System.Console.Write(mask);
            }
        }

        System.Console.WriteLine();
        IntPtr ptr = new IntPtr();
        ptr = Marshal.SecureStringToBSTR(securePass);
        string plainPass = Marshal.PtrToStringBSTR(ptr);
        Marshal.ZeroFreeBSTR(ptr);
        return plainPass;
    }

Solution 9 - C#

Here's a version that adds support for the Escape key (which returns a null string)

public static string ReadPassword()
{
    string password = "";
    while (true)
    {
        ConsoleKeyInfo key = Console.ReadKey(true);
        switch (key.Key)
        {
            case ConsoleKey.Escape:
                return null;
            case ConsoleKey.Enter:
                return password;
            case ConsoleKey.Backspace:
                if (password.Length > 0) 
                {
                    password = password.Substring(0, (password.Length - 1));
                    Console.Write("\b \b");
                }
                break;
            default:
                password += key.KeyChar;
                Console.Write("*");
                break;
        }
    }
}

Solution 10 - C#

(My) nuget package to do this, based on the top answer:

install-package PanoramicData.ConsoleExtensions

Usage:

using PanoramicData.ConsoleExtensions;

...

Console.Write("Password: ");
var password = ConsolePlus.ReadPassword();
Console.WriteLine();

Project URL: https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

Pull requests welcome.

Solution 11 - C#

Jeez guys

    static string ReadPasswordLine()
    {
        string pass = "";
        ConsoleKeyInfo key;
        do
        {
            key = Console.ReadKey(true);
            if (key.Key != ConsoleKey.Enter)
            {
                if (!(key.KeyChar < ' '))
                {
                    pass += key.KeyChar;
                    Console.Write("*");
                }
                else if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
                {
                    Console.Write(Convert.ToChar(ConsoleKey.Backspace));
                    pass = pass.Remove(pass.Length - 1);
                    Console.Write(" ");
                    Console.Write(Convert.ToChar(ConsoleKey.Backspace));
                }
            }
        } while (key.Key != ConsoleKey.Enter);
        return pass;
    }

Solution 12 - C#

You could append your keys to an accumulating linked list.

When a backspace key is received, remove the last key from the list.

When you receive the enter key, collapse your list into a string and do the rest of your work.

Solution 13 - C#

I made some changes for backspace

        string pass = "";
        Console.Write("Enter your password: ");
        ConsoleKeyInfo key;

        do
        {
            key = Console.ReadKey(true);

            // Backspace Should Not Work
            if (key.Key != ConsoleKey.Backspace)
            {
                pass += key.KeyChar;
                Console.Write("*");
            }
            else
            {
                pass = pass.Remove(pass.Length - 1);
                Console.Write("\b \b");
            }
        }
        // Stops Receving Keys Once Enter is Pressed
        while (key.Key != ConsoleKey.Enter);

        Console.WriteLine();
        Console.WriteLine("The Password You entered is : " + pass);

Solution 14 - C#

I have updated Ronnie's version after spending way too much time trying to enter a password only to find out that I had my CAPS LOCK on!

With this version what ever the message is in _CapsLockMessage will "float" at the end of the typing area and will be displayed in red.

This version takes a bit more code and does require a polling loop. On my computer CPU usage about 3% to 4%, but one could always add a small Sleep() value to decrease CPU usage if needed.

    private const string _CapsLockMessage = " CAPS LOCK";

    /// <summary>
    /// Like System.Console.ReadLine(), only with a mask.
    /// </summary>
    /// <param name="mask">a <c>char</c> representing your choice of console mask</param>
    /// <returns>the string the user typed in</returns>
    public static string ReadLineMasked(char mask = '*')
    {
        // Taken from http://stackoverflow.com/a/19770778/486660
        var consoleLine = new StringBuilder();
        ConsoleKeyInfo keyInfo;
        bool isDone;
        bool isAlreadyLocked;
        bool isCapsLockOn;
        int cursorLeft;
        int cursorTop;
        ConsoleColor originalForegroundColor;

        isDone = false;
        isAlreadyLocked = Console.CapsLock;

        while (isDone == false)
        {
            isCapsLockOn = Console.CapsLock;
            if (isCapsLockOn != isAlreadyLocked)
            {
                if (isCapsLockOn)
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    originalForegroundColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("{0}", _CapsLockMessage);
                    Console.SetCursorPosition(cursorLeft, cursorTop);
                    Console.ForegroundColor = originalForegroundColor;
                }
                else
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    Console.Write("{0}", string.Empty.PadRight(_CapsLockMessage.Length));
                    Console.SetCursorPosition(cursorLeft, cursorTop);
                }
                isAlreadyLocked = isCapsLockOn;
            }

            if (Console.KeyAvailable)
            {
                keyInfo = Console.ReadKey(intercept: true);

                if (keyInfo.Key == ConsoleKey.Enter)
                {
                    isDone = true;
                    continue;
                }

                if (!char.IsControl(keyInfo.KeyChar))
                {
                    consoleLine.Append(keyInfo.KeyChar);
                    Console.Write(mask);
                }
                else if (keyInfo.Key == ConsoleKey.Backspace && consoleLine.Length > 0)
                {
                    consoleLine.Remove(consoleLine.Length - 1, 1);

                    if (Console.CursorLeft == 0)
                    {
                        Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                        Console.Write(' ');
                        Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                    }
                    else
                    {
                        Console.Write("\b \b");
                    }
                }

                if (isCapsLockOn)
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    originalForegroundColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("{0}", _CapsLockMessage);
                    Console.CursorLeft = cursorLeft;
                    Console.CursorTop = cursorTop;
                    Console.ForegroundColor = originalForegroundColor;
                }
            }
        }

        Console.WriteLine();

        return consoleLine.ToString();
    }

Solution 15 - C#

Here is my simple version. Every time you hit a key, delete all from console and draw as many '*' as the length of password string is.

int chr = 0;
string pass = "";
const int ENTER = 13;
const int BS = 8;

do
{
   chr = Console.ReadKey().KeyChar;
   Console.Clear(); //imediately clear the char you printed

   //if the char is not 'return' or 'backspace' add it to pass string
   if (chr != ENTER && chr != BS) pass += (char)chr;

   //if you hit backspace remove last char from pass string
   if (chr == BS) pass = pass.Remove(pass.Length-1, 1);

   for (int i = 0; i < pass.Length; i++)
   {
      Console.Write('*');
   }
} 
 while (chr != ENTER);

Console.Write("\n");
Console.Write(pass);

Console.Read(); //just to see the pass

Solution 16 - C#

If I understand this correctly, you're trying to make backspace delete both the visible * character on screen and the cached character in your pass variable?

If so, then just change your else block to this:

            else
            {
                Console.Write("\b");
                pass = pass.Remove(pass.Length -1);
            }

Solution 17 - C#

 string pass = "";
 Console.WriteLine("Enter your password: ");
 ConsoleKeyInfo key;

 do {
  key = Console.ReadKey(true);

  if (key.Key != ConsoleKey.Backspace) {
   pass += key.KeyChar;
   Console.Write("*");
  } else {
   Console.Write("\b \b");
   char[] pas = pass.ToCharArray();
   string temp = "";
   for (int i = 0; i < pass.Length - 1; i++) {
    temp += pas[i];
   }
   pass = temp;
  }
 }
 // Stops Receving Keys Once Enter is Pressed
 while (key.Key != ConsoleKey.Enter);

 Console.WriteLine();
 Console.WriteLine("The Password You entered is : " + pass);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMohammad NadeemView Question on Stackoverflow
Solution 1 - C#CraigTPView Answer on Stackoverflow
Solution 2 - C#Damian Leszczyński - VashView Answer on Stackoverflow
Solution 3 - C#shermyView Answer on Stackoverflow
Solution 4 - C#MDMoore313View Answer on Stackoverflow
Solution 5 - C#Ronnie OverbyView Answer on Stackoverflow
Solution 6 - C#Rich SView Answer on Stackoverflow
Solution 7 - C#Rafał KłysView Answer on Stackoverflow
Solution 8 - C#Stephen WilsonView Answer on Stackoverflow
Solution 9 - C#Sven VranckxView Answer on Stackoverflow
Solution 10 - C#David BondView Answer on Stackoverflow
Solution 11 - C#Damian K.View Answer on Stackoverflow
Solution 12 - C#sarnoldView Answer on Stackoverflow
Solution 13 - C#user2346303View Answer on Stackoverflow
Solution 14 - C#JimView Answer on Stackoverflow
Solution 15 - C#svicarView Answer on Stackoverflow
Solution 16 - C#user302845View Answer on Stackoverflow
Solution 17 - C#Neeraj KumarView Answer on Stackoverflow