Aplicación de consola de enmascaramiento de contraseña

201

Intenté el siguiente código ...

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);

Pero de esta manera la funcionalidad de retroceso no funciona al escribir la contraseña. ¿Cualquier sugerencia?

Mohammad Nadeem
fuente
11
Le sugiero que no repita nada en la consola porque eso expondrá la longitud de la contraseña.
Ray Cheng
8
@RayCheng: bastante justo, pero muy pocas interfaces de usuario (que no sean en algunos sistemas Unix) no hacen eco en absoluto. Para una experiencia de usuario coherente con otras aplicaciones y sitios web, probablemente sea mejor mostrar los caracteres *.
Stephen Holt
44
@StephenHolt Estoy bastante seguro de que cada entrada de contraseña basada en terminal que he encontrado nunca ha elegido hacer eco en el terminal. Dados los beneficios de seguridad y el hecho de que esta es una convención bien conocida en el mundo de Unix, personalmente creo que hacer eco de nada es la elección correcta, a menos que crea que su base de usuarios probablemente no esté familiarizada con el uso de terminales (en cuyo caso probablemente sea mejor usar una GUI de todos modos).
Ajedi32

Respuestas:

225

Console.Write("\b \b");eliminará el carácter de asterisco de la pantalla, pero no tiene ningún código dentro de su elsebloque que elimine el carácter ingresado previamente de supass variable de cadena.

Aquí está el código de trabajo relevante que debe hacer lo que necesita:

string pass = "";
do
{
    ConsoleKeyInfo key = Console.ReadKey(true);
    // Backspace Should Not Work
    if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
    {
        pass += key.KeyChar;
        Console.Write("*");
    }
    else
    {
        if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
        {
            pass = pass.Substring(0, (pass.Length - 1));
            Console.Write("\b \b");
        }
        else if(key.Key == ConsoleKey.Enter)
        {
            break;
        }
    }
} while (true);
CraigTP
fuente
2
Oh, pensé que \ b \ b me llevará de regreso a dos lugares. Sin embargo, esto parece estar funcionando perfectamente.
Mohammad Nadeem
8
@Nadeem: tenga en cuenta el carácter de espacio ( ' ') entre los caracteres de retroceso ( '\b'). "\b \b"lo lleva a un lugar hacia atrás, luego imprime un espacio (que lo lleva a un lugar hacia adelante) y luego lo lleva de nuevo, para que termine donde estaba el '*'carácter eliminado .
dtb
14
@Nadeem - Los primeros \bse mueve el cursor una posición hacia atrás (ahora por debajo de la última *Char Los. [space]"Imprime más de" carácter del asterisco, pero también se mueve el cursor un carácter hacia adelante de nuevo, por lo que los últimos \bmovimientos del cursor a la parte de atrás donde el último *usado para be! (Uf - ¡Espero que tenga sentido!)
CraigTP
3
if (pass.Length > 0)debería ser de if (key.Key == ConsoleKey.Backspace && pass.Length > 0)otra manera no obtendrá el último carácter de la contraseña ..
MemphiZ
9
Si no desea que el usuario pueda escribir caracteres de control (como F5 o Escape), puede reemplazarlo if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)por if (!char.IsControl(key.KeyChar)).
Safron
90

Para esto, debe usar 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;
}
Damian Leszczyński - Vash
fuente
Esto solo me llevará dos lugares atrás. Pero lo que necesito es que cuando presiono Retroceso, el último carácter debe eliminarse. Al igual que la funcionalidad original del retroceso.
Mohammad Nadeem
1
Necesitaba anidar if( pwd.Length > 0)en la primera declaración para evitar que la gente elimine la pregunta :)
Dead.Rabit
1
De manera similar al comentario de Safron sobre la respuesta actualmente aceptada, la elsecláusula final se beneficiaría de una prueba if (!char.IsControl(i.KeyChar))(o al menos if (i.KeyChar != '\u0000')).
Peter Taylor
1
¿Cómo puedo convertir esa contraseña en una cadena?
Joseph Kreifels II
47

Solución completa, Vanilla C # .net 3.5+

Cortar pegar :)

    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('*');
            }
        }
    }
Shermy
fuente
3
Siempre puede ser más difícil :) No funcionará en Mac / Linux porque no se reconoce la nueva línea. Environment.NewLine tiene la cadena para una nueva línea. Así que modifiqué esto en: while (! Environment.NewLine.Contains (chr = System.Console.ReadKey (true) .KeyChar))
Hugo Logmans
19

Tomando la respuesta principal, así como las sugerencias de sus comentarios, y modificándola para usar SecureString en lugar de String, pruebe todas las teclas de control y no cometa errores o escriba un "*" extra en la pantalla cuando la longitud de la contraseña sea 0, mi solución es:

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;
}
MDMoore313
fuente
15

El mío ignora los caracteres de control y maneja el ajuste de línea:

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();
}
Ronnie Overby
fuente
Funciona perfectamente. Sin embargo, es posible que deba agregar código para que el DELETE personaje borre todo el texto ingresado. Su secuencia de teclas es CTRL + BACKSPACEy su código char es 0x7f.
Alex Essilfie
9

Leer la entrada de la consola es difícil, necesita manejar teclas especiales como Ctrl, Alt, también teclas de cursor y Retroceso / Eliminar. En algunos diseños de teclado, como el Ctrl sueco, incluso es necesario para ingresar teclas que existen directamente en el teclado de EE. UU. Creo que tratar de manejar esto usando el "bajo nivel"Console.ReadKey(true) es muy difícil, por lo que la forma más fácil y más robusta es simplemente deshabilitar el "eco de entrada de la consola" al ingresar la contraseña con un poco de WINAPI.

El siguiente ejemplo se basa en la respuesta a Leer una contraseña de la pregunta std :: cin .

    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;
    }
Rafał Kłys
fuente
9

Esto enmascara la contraseña con un cuadrado rojo, luego vuelve a los colores originales una vez que se ha ingresado la contraseña.

No impide que el usuario use copiar / pegar para obtener la contraseña, pero si se trata más bien de detener a alguien que mira por encima de su hombro, esta es una buena solución rápida.

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;
Rico S
fuente
El único problema es que si retrocede, el fondo donde tenía los caracteres permanece en rojo. Preferiría configurar ForegroundColor como origBG para tener una entrada de contraseña de estilo Linux.
mababin
1
También podría hacerlo Console.CursorVisible=falsey volver a establecerlo en el valor anterior. Esto evitaría que alguien alcanzara el pico en la longitud de la contraseña.
mababin
6

Encontré un error en la solución vainilla C # 3.5 .NET de Shermy que de lo contrario funciona a la perfección. También he incorporado Damian Leszczyński - la idea SecureString de Vash aquí, pero puedes usar una cadena normal si lo prefieres.

EL ERROR: Si presiona la tecla de retroceso durante la solicitud de contraseña y la longitud actual de la contraseña es 0, entonces se inserta incorrectamente un asterisco en la máscara de contraseña. Para corregir este error, modifique el siguiente método.

    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;
    }
Stephen Wilson
fuente
4

Aquí hay una versión que agrega soporte para la Escapeclave (que devuelve una nullcadena)

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;
        }
    }
}
Sven Vranckx
fuente
3

(Mi) paquete nuget para hacer esto, basado en la respuesta principal:

install-package PanoramicData.ConsoleExtensions

Uso:

using PanoramicData.ConsoleExtensions;

...

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

URL del proyecto: https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

Solicitudes de extracción bienvenidas.

David Bond
fuente
Usé esto para un pequeño proyecto. Funcionó como se esperaba. Gracias
gmalenko
1

Puede agregar sus claves a una lista vinculada acumulada.

Cuando se recibe una tecla de retroceso, elimine la última tecla de la lista.

Cuando reciba la tecla Intro, contraiga su lista en una cadena y haga el resto de su trabajo.

sarnold
fuente
Suena factible, pero ¿cómo eliminaré el último personaje de la pantalla?
Mohammad Nadeem
1

Hice algunos cambios para retroceder

        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);
usuario2346303
fuente
1

Aquí está mi versión simple. Cada vez que presiona una tecla, elimine todo de la consola y dibuje tantos '*' como la longitud de la cadena de contraseña.

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
svicar
fuente
0

¡He actualizado la versión de Ronnie después de pasar demasiado tiempo tratando de ingresar una contraseña solo para descubrir que tenía mi BLOQ MAYÚS activado!

Con esta versión, el mensaje _CapsLockMessage"flotará" al final del área de escritura y se mostrará en rojo.

Esta versión requiere un poco más de código y requiere un bucle de sondeo. En mi computadora, el uso de CPU es de aproximadamente 3% a 4%, pero siempre se puede agregar un pequeño valor Sleep () para disminuir el uso de CPU si es necesario.

    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();
    }
Jim
fuente
-1

Si entiendo esto correctamente, ¿está tratando de hacer que la tecla de retroceso elimine tanto el carácter visible * en la pantalla como el carácter en caché en su variable de paso?

Si es así, simplemente cambie su bloque else a esto:

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

fuente
1
Esto funcionará bien, excepto que la eliminación del carácter por retroceso no se mostrará.
Mohammad Nadeem
-2
 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);
Neeraj Kumar
fuente
1
Esta respuesta no agrega nada más allá de las respuestas existentes. Además, las buenas respuestas generalmente deberían explicar el código, en lugar de simplemente pegar el código en el cuadro de respuesta. Lea Cómo responder
durron597