Password masking console application

166,982

Solution 1

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

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

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

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

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();
}
Share:
166,982

Related videos on Youtube

Mohammad Nadeem
Author by

Mohammad Nadeem

Updated on August 04, 2021

Comments

  • Mohammad Nadeem
    Mohammad Nadeem over 2 years

    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?

    • Ray Cheng
      Ray Cheng over 11 years
      I suggest you do not echo anything back to the console because that'll expose the length of the password.
    • Stephen Holt
      Stephen Holt about 10 years
      @RayCheng - fair enough, but very few user interfaces (other than on some Unix systems) echo nothing at all. For consistent user experience with other apps and websites, showing the * characters is probably best.
    • Ajedi32
      Ajedi32 over 7 years
      @StephenHolt I'm fairly certain every terminal-based password input I've ever encountered chose to echo nothing to the terminal. Given the security benefits and the fact that this is a well-known convention in the Unix world, I personally think echoing nothing is the right choice, unless you believe your user base is likely to be unfamiliar with the use of terminals (in which case it's probably best to use a GUI instead anyway).
  • Mohammad Nadeem
    Mohammad Nadeem over 13 years
    This will only take me two places back. But what I need is that when I press Backspace the last character should be deleted. Just like the original functinality of backspace.
  • Mohammad Nadeem
    Mohammad Nadeem over 13 years
    Sounds achievable but how will I remove the last character from the display.
  • Mohammad Nadeem
    Mohammad Nadeem over 13 years
    This will work fine except that the deletion of the character by backspace will not be displayed.
  • Mohammad Nadeem
    Mohammad Nadeem over 13 years
    Oh I thought \b \b will take me two places back. Nevertheless this seems to be working prfecttly.
  • Feidex
    Feidex over 13 years
    @Nadeem: Note the space character (' ') between the backspace characters ('\b'). "\b \b" takes you one place back, then prints a space (which takes you one place forward) and then takes you back again, so you end up where the deleted '*' character was.
  • DMpal  Jain
    DMpal Jain over 13 years
    @Nadeem - The first \b moves the cursor back one position (now underneath the last * char. The [space] character "prints over" the asterisk, but also moves the cursor one character forward again, so the last \b moves the cursor back to where the last * used to be! (Phew - Hope that makes sense!)
  • Dead.Rabit
    Dead.Rabit almost 12 years
    I needed to nest if( pwd.Length > 0) into the first else statement to stop people deleting the question :)
  • MemphiZ
    MemphiZ over 11 years
    if (pass.Length > 0) should be if (key.Key == ConsoleKey.Backspace && pass.Length > 0) otherwise you will not get the last character of the password..
  • DMpal  Jain
    DMpal Jain over 11 years
    @MemphiZ - Good catch! Edited to reflect the fix.
  • Safron
    Safron almost 9 years
    If you don't want the user to be able to write control characters (like F5 or Escape), you could replace if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) with if (!char.IsControl(key.KeyChar)).
  • durron597
    durron597 over 8 years
    This answer does not add anything beyond what existing answers to. Additionally, good answers should typically explain the code, rather than just pasting code in to the answer box. Please read How to Answer
  • Peter Taylor
    Peter Taylor over 8 years
    Similarly to Safron's comment on the currently accepted answer, the final else clause would benefit from a test if (!char.IsControl(i.KeyChar)) (or at the very least if (i.KeyChar != '\u0000')).
  • Alex Essilfie
    Alex Essilfie almost 7 years
    Works perfectly. You may have to add code to make the DELETE character erase all entered text though. Its key sequence is CTRL + BACKSPACE and its char code is 0x7f.
  • Selman Genç
    Selman Genç over 6 years
    This didn't work on me. It always left an asterisk at the end. I needed to add one space before the backspace to make it work: Console.Write(" \b \b");
  • Hugo Logmans
    Hugo Logmans almost 6 years
    It can always be more difficult :) Won't work on Mac/Linux because the newline is not recognised. Environment.NewLine has the string for a newline. So I modified this into: while (!Environment.NewLine.Contains(chr = System.Console.ReadKey(true).KeyChar))
  • mababin
    mababin over 5 years
    The only problem is that if you do backspace the background where you had characters stays red. I would rather go with setting ForegroundColor as origBG to have a Linux style password input.
  • mababin
    mababin over 5 years
    You could also do Console.CursorVisible=false and set it back to previous value after. This would prevent someone to peak at password length.
  • Joseph Kreifels II
    Joseph Kreifels II over 5 years
    How can I turn that password into a string?
  • plr108
    plr108 almost 5 years
    pass.Substring(0, (pass.Length - 1)) could be simplified to pass.Substring(0, pass.Length - 1)
  • Amessihel
    Amessihel over 4 years
  • jpaugh
    jpaugh about 4 years
    @Amessihel While that document makes a good case for not using credentials, if you have to use them anyway, SecureString is better than nothing.
  • gmalenko
    gmalenko almost 4 years
    I used this for a small project. Worked as expected. Thank you
  • SBF
    SBF over 3 years
    Hmm, what about backspace etc...?
  • Nabakamal Das
    Nabakamal Das almost 2 years
    Works as promised.