Applicazione console di mascheramento password


201

Ho provato il seguente codice ...

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

Ma in questo modo la funzionalità backspace non funziona durante la digitazione della password. Qualche suggerimento?


11
Ti suggerisco di non ripetere nulla alla console perché ciò esporrà la lunghezza della password.
Ray Cheng,

8
@RayCheng - abbastanza equo, ma pochissime interfacce utente (tranne che su alcuni sistemi Unix) non fanno eco. Per un'esperienza utente coerente con altre app e siti Web, mostrare i caratteri * è probabilmente il migliore.
Stephen Holt,

4
@StephenHolt Sono abbastanza sicuro che ogni input di password basato su terminale che abbia mai incontrato abbia scelto di non fare eco al terminale. Dati i vantaggi in termini di sicurezza e il fatto che si tratta di una convenzione ben nota nel mondo Unix, penso personalmente che fare eco a nulla sia la scelta giusta, a meno che non si ritenga che la propria base di utenti non abbia familiarità con l'uso dei terminali (nel qual caso probabilmente è meglio usare una GUI invece).
Ajedi32,

Risposte:


225

Console.Write("\b \b");eliminerà il carattere asterisco dallo schermo, ma non hai alcun codice nel elseblocco che rimuova il carattere precedentemente immesso dalla passvariabile stringa.

Ecco il codice di lavoro pertinente che dovrebbe fare ciò di cui hai bisogno:

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

2
Oh, ho pensato che \ b \ b mi avrebbe riportato indietro di due posti. Tuttavia, questo sembra funzionare perfettamente.
Mohammad Nadeem,

8
@Nadeem: nota il carattere spazio ( ' ') tra i caratteri backspace ( '\b'). "\b \b"ti riporta indietro di un posto, quindi stampa uno spazio (che ti porta avanti di un posto) e poi ti riporta indietro di nuovo, così finisci dove si trovava il '*'personaggio eliminato .
dtb,

14
@Nadeem - Le prime \bmosse il cursore indietro di una posizione (ora sotto l'ultimo *carattere I. [space]Carattere "stampe oltre" l'asterisco, ma si muove anche il carattere cursore di una di nuovo in avanti, così gli ultimi \bmovimenti del cursore posteriore dove l'ultimo *utilizzato per be! (Phew - Spero che abbia un senso!)
CraigTP

3
if (pass.Length > 0)in caso if (key.Key == ConsoleKey.Backspace && pass.Length > 0)contrario non otterrai l'ultimo carattere della password.
MemphiZ,

9
Se non desideri che l'utente sia in grado di scrivere caratteri di controllo (come F5 o Escape), puoi sostituirlo if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)con if (!char.IsControl(key.KeyChar)).
Safron,

90

Per questo è necessario utilizzare 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;
}

Questo mi riporterà solo due posti indietro. Ma quello di cui ho bisogno è che quando premo Backspace l'ultimo carattere dovrebbe essere cancellato. Proprio come la funzionalità originale di backspace.
Mohammad Nadeem,

1
Avevo bisogno di annidare if( pwd.Length > 0)nella prima dichiarazione per impedire alle persone di eliminare la domanda :)
Dead.Rabit

1
Analogamente al commento di Safron sulla risposta attualmente accettata, la elseclausola finale trarrebbe beneficio da un test if (!char.IsControl(i.KeyChar))(o almeno if (i.KeyChar != '\u0000')).
Peter Taylor,

1
Come posso trasformare quella password in una stringa?
Joseph Kreifels II,


47

Soluzione completa, vaniglia C # .net 3.5+

Taglia e incolla :)

    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('*');
            }
        }
    }

3
Può sempre essere più difficile :) Non funzionerà su Mac / Linux perché la nuova riga non viene riconosciuta. Environment.NewLine ha la stringa per una nuova riga. Quindi l'ho modificato in: while (! Environment.NewLine.Contains (chr = System.Console.ReadKey (true) .KeyChar))
Hugo Logmans,

19

Prendendo la risposta migliore, così come i suggerimenti dai suoi commenti, e modificandola per utilizzare SecureString invece di String, testare tutti i tasti di controllo e non errori o scrivere un "*" extra sullo schermo quando la lunghezza della password è 0, la mia soluzione è:

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

15

Il mio ignora i caratteri di controllo e gestisce il ritorno a capo:

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

Funziona perfettamente. Potrebbe essere necessario aggiungere un codice per fare in modo che il DELETE carattere cancelli tutto il testo inserito. La sequenza di tasti è CTRL + BACKSPACEe il suo codice char è 0x7f.
Alex Essilfie,

9

La lettura dell'input della console è difficile, è necessario gestire tasti speciali come Ctrl, Alt, anche i tasti cursore e Backspace / Delete. Su alcuni layout di tastiera, come lo svedese Ctrl è persino necessario inserire i tasti che esistono direttamente sulla tastiera americana. Credo che provare a gestirlo usando il "basso livello" Console.ReadKey(true)sia solo molto difficile, quindi il modo più semplice e robusto è semplicemente disabilitare "eco input console" durante l'immissione della password usando un po 'di WINAPI.

L'esempio seguente si basa sulla risposta a Leggi una password dalla domanda 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;
    }

9

In questo modo la password viene mascherata da un quadrato rosso, quindi torna ai colori originali una volta immessa la password.

Non impedisce all'utente di usare copia / incolla per ottenere la password, ma se si tratta più di fermare qualcuno che ti guarda alle spalle, questa è una buona soluzione rapida.

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;

L'unico problema è che se si esegue il backspace lo sfondo in cui si trovavano i personaggi rimane rosso. Preferirei impostare ForegroundColor come origBG per inserire una password in stile Linux.
mababin,

1
Puoi anche farlo Console.CursorVisible=falsee riportarlo al valore precedente dopo. Ciò impedirebbe a qualcuno di raggiungere il picco alla lunghezza della password.
mababin,

6

Ho trovato un bug nella soluzione .NET di C # 3.5 alla vaniglia di Shermy che altrimenti funzionerebbe. Ho anche incorporato Damian Leszczyński - l'idea SecureString di Vash qui ma puoi usare una stringa normale se preferisci.

IL BUG: Se si preme backspace durante la richiesta della password e la lunghezza corrente della password è 0, un asterisco viene inserito erroneamente nella maschera della password. Per correggere questo errore, modifica il seguente metodo.

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

4

Ecco una versione che aggiunge il supporto per la Escapechiave (che restituisce una nullstringa)

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

3

(Mio) pacchetto nuget per fare questo, in base alla risposta principale:

install-package PanoramicData.ConsoleExtensions

Uso:

using PanoramicData.ConsoleExtensions;

...

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

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

Pull richieste di benvenuto.


L'ho usato per un piccolo progetto. Ha funzionato come previsto. Grazie
gmalenko il

1

È possibile aggiungere le chiavi a un elenco di collegamenti accumulati.

Quando viene ricevuta una chiave backspace, rimuovere l'ultima chiave dall'elenco.

Quando ricevi la chiave di invio, comprimi il tuo elenco in una stringa e fai il resto del tuo lavoro.


Sembra realizzabile, ma come rimuoverò l'ultimo personaggio dal display.
Mohammad Nadeem,

1

Ho apportato alcune modifiche al 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);

1

Ecco la mia versione semplice. Ogni volta che premi un tasto, elimina tutto dalla console e disegna tanti "*" quanti sono la lunghezza della stringa della password.

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

0

Ho aggiornato la versione di Ronnie dopo aver trascorso troppo tempo cercare di inserire una password solo per scoprire che avevo il BLOC MAIUSC attivato!

Con questa versione, qualunque sia il messaggio _CapsLockMessage, "fluttuerà" alla fine dell'area di digitazione e verrà visualizzato in rosso.

Questa versione richiede un po 'più di codice e richiede un ciclo di polling. Sul mio computer l'utilizzo della CPU da circa il 3% al 4%, ma si potrebbe sempre aggiungere un piccolo valore Sleep () per ridurre l'utilizzo della CPU, se necessario.

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

-1

Se lo capisco correttamente, stai cercando di fare in modo che backspace elimini sia il carattere visibile * sullo schermo sia il carattere memorizzato nella cache nella variabile pass?

In tal caso, cambia semplicemente il tuo altro blocco in questo:

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

1
Questo funzionerà benissimo, tranne per il fatto che la cancellazione del personaggio da parte del backspace non verrà visualizzata.
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);

1
Questa risposta non aggiunge nulla al di là delle risposte esistenti. Inoltre, le buone risposte dovrebbero in genere spiegare il codice, piuttosto che incollare il codice nella casella di risposta. Si prega di leggere come rispondere
durron597
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.