Application de console de masquage de mot de passe


201

J'ai essayé le code suivant ...

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

Mais de cette façon, la fonctionnalité de retour arrière ne fonctionne pas lors de la saisie du mot de passe. Toute suggestion?


11
Je vous suggère de ne pas renvoyer quoi que ce soit à la console, car cela exposera la longueur du mot de passe.
Ray Cheng

8
@RayCheng - assez juste, mais très peu d'interfaces utilisateur (autres que sur certains systèmes Unix) ne font écho à rien du tout. Pour une expérience utilisateur cohérente avec d'autres applications et sites Web, il est probablement préférable d'afficher les caractères *.
Stephen Holt

4
@StephenHolt Je suis pratiquement certain que chaque entrée de mot de passe basée sur un terminal que j'ai jamais rencontrée a choisi de ne faire écho à aucun message au terminal. Compte tenu des avantages en matière de sécurité et du fait qu'il s'agit d'une convention bien connue dans le monde Unix, je pense personnellement que ne faire écho à rien est le bon choix, sauf si vous pensez que votre base d'utilisateurs n'est probablement pas familière avec l'utilisation de terminaux (auquel cas il est probablement préférable d'utiliser une interface graphique à la place de toute façon).
Ajedi32 du

Réponses:


225

Console.Write("\b \b");supprimera le caractère astérisque de l'écran, mais vous n'avez aucun code dans votre elsebloc qui supprime le caractère entré précédemment de votre passvariable de chaîne.

Voici le code de travail approprié qui devrait faire ce dont vous avez besoin:

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, je pensais que \ b \ b me ramènerait de deux places. Néanmoins, cela semble fonctionner correctement.
Mohammad Nadeem

8
@Nadeem: Notez le caractère espace ( ' ') entre les caractères de retour arrière ( '\b'). "\b \b"vous ramène d'un endroit, puis imprime un espace (ce qui vous fait avancer d'un endroit), puis vous ramène à nouveau, vous vous retrouvez donc là où se trouvait le '*'caractère supprimé .
dtb

14
@Nadeem - Le premier \brecule le curseur d'une position (maintenant sous le dernier caractère *. Le [space]caractère "imprime" sur l'astérisque, mais déplace également le curseur d'un caractère de nouveau vers l'avant, donc le dernier \bramène le curseur à l'endroit où le dernier était *utilisé pour être! (Ouf - J'espère que cela a du sens!)
CraigTP

3
if (pass.Length > 0)devrait être if (key.Key == ConsoleKey.Backspace && pass.Length > 0)sinon vous n'obtiendrez pas le dernier caractère du mot de passe ..
MemphiZ

9
Si vous ne voulez pas que l'utilisateur puisse écrire des caractères de contrôle (comme F5 ou Escape), vous pouvez le remplacer if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)par if (!char.IsControl(key.KeyChar)).
Safron

90

Pour cela, vous devez utiliser 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;
}

Cela ne me ramènera que de deux places. Mais ce dont j'ai besoin, c'est que lorsque j'appuie sur Retour arrière, le dernier caractère doit être supprimé. Tout comme la fonction originale du retour arrière.
Mohammad Nadeem

1
J'avais besoin de me nicher if( pwd.Length > 0)dans la première déclaration else pour empêcher les gens de supprimer la question :)
Dead.Rabit

1
De même que le commentaire de Safron sur la réponse actuellement acceptée, la elseclause finale bénéficierait d'un test if (!char.IsControl(i.KeyChar))(ou à tout le moins if (i.KeyChar != '\u0000')).
Peter Taylor

1
Comment puis-je transformer ce mot de passe en chaîne?
Joseph Kreifels II


47

Solution complète, vanille C # .net 3.5+

Couper coller :)

    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
Cela peut toujours être plus difficile :) Ne fonctionnera pas sur Mac / Linux car la nouvelle ligne n'est pas reconnue. Environment.NewLine a la chaîne pour une nouvelle ligne. J'ai donc modifié ceci en: while (! Environment.NewLine.Contains (chr = System.Console.ReadKey (true) .KeyChar))
Hugo Logmans

19

En prenant la première réponse, ainsi que les suggestions de ses commentaires, et en la modifiant pour utiliser SecureString au lieu de String, testez toutes les clés de contrôle, et non pas d'erreur ou écrivez un "*" supplémentaire à l'écran lorsque la longueur du mot de passe est 0, ma solution est:

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

Le mien ignore les caractères de contrôle et gère le retour à la ligne:

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

Fonctionne parfaitement. Vous devrez peut-être ajouter du code pour que le DELETE caractère efface tout le texte saisi. Sa séquence de touches est CTRL + BACKSPACEet son code de caractère est 0x7f.
Alex Essilfie

9

La lecture de l'entrée de la console est difficile, vous devez gérer les touches spéciales comme Ctrl, Alt, ainsi que les touches de curseur et Retour arrière / Supprimer. Sur certaines dispositions de clavier, comme le suédois Ctrl est même nécessaire pour entrer des touches qui existent directement sur le clavier américain. Je crois qu'essayer de gérer cela en utilisant le "bas niveau" Console.ReadKey(true)est juste très difficile, donc le moyen le plus simple et le plus robuste est de simplement désactiver "l'écho d'entrée de la console" lors de la saisie du mot de passe en utilisant un peu de WINAPI.

L'exemple ci-dessous est basé sur la réponse à Lire un mot de passe de la question 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

Cela masque le mot de passe avec un carré rouge, puis revient aux couleurs d'origine une fois le mot de passe entré.

Cela n'empêche pas l'utilisateur d'utiliser le copier / coller pour obtenir le mot de passe, mais s'il s'agit plus simplement d'empêcher quelqu'un de regarder par-dessus votre épaule, c'est une bonne solution rapide.

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;

Le seul problème est que si vous effectuez un retour arrière, l'arrière-plan où vous aviez des caractères reste rouge. Je préfère choisir ForegroundColor comme origBG pour avoir une entrée de mot de passe de style Linux.
mababin

1
Vous pouvez également le faire Console.CursorVisible=falseet le remettre à la valeur précédente après. Cela empêcherait quelqu'un d'atteindre un pic de longueur de mot de passe.
mababin

6

J'ai trouvé un bogue dans la solution vanille C # 3.5 .NET de shermy qui fonctionne autrement. J'ai également incorporé Damian Leszczyński - l'idée SecureString de Vash ici, mais vous pouvez utiliser une chaîne ordinaire si vous préférez.

LE BOGUE: si vous appuyez sur la touche de retour arrière pendant l'invite de mot de passe et que la longueur actuelle du mot de passe est 0, un astérisque n'est pas inséré correctement dans le masque de mot de passe. Pour corriger ce bogue, modifiez la méthode suivante.

    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

Voici une version qui ajoute la prise en charge de la Escapeclé (qui renvoie une nullchaîne)

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

(Mon) paquet nuget pour ce faire, basé sur la réponse la plus élevée:

install-package PanoramicData.ConsoleExtensions

Usage:

using PanoramicData.ConsoleExtensions;

...

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

URL du projet: https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

Les demandes de tirage sont les bienvenues.


Je l'ai utilisé pour un petit projet. A fonctionné comme prévu. Merci
gmalenko

1

Vous pouvez ajouter vos clés à une liste liée accumulée.

Lorsqu'une clé de retour arrière est reçue, supprimez la dernière clé de la liste.

Lorsque vous recevez la clé d'entrée, réduisez votre liste en chaîne et effectuez le reste de votre travail.


Cela semble réalisable, mais comment supprimer le dernier caractère de l'écran.
Mohammad Nadeem

1

J'ai fait quelques changements pour le retour arrière

        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

Voici ma version simple. Chaque fois que vous appuyez sur une touche, supprimez tout de la console et tracez autant de '*' que la longueur de la chaîne de mot de passe.

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

J'ai mis à jour la version de Ronnie après avoir passé beaucoup trop de temps à essayer d'entrer un mot de passe pour découvrir que j'avais mon CAPS LOCK!

Avec cette version, le contenu du message _CapsLockMessage"flottera" à la fin de la zone de saisie et sera affiché en rouge.

Cette version prend un peu plus de code et nécessite une boucle d'interrogation. Sur mon ordinateur, l'utilisation du processeur est d'environ 3% à 4%, mais on peut toujours ajouter une petite valeur Sleep () pour réduire l'utilisation du processeur si nécessaire.

    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

Si je comprends bien, vous essayez de faire effacer à la fois le caractère visible * à l'écran et le caractère mis en cache dans votre variable de passe?

Si c'est le cas, changez simplement votre bloc else en ceci:

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

1
Cela fonctionnera bien sauf que la suppression du caractère par retour arrière ne sera pas affichée.
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
Cette réponse n'ajoute rien au-delà des réponses existantes. De plus, les bonnes réponses devraient généralement expliquer le code, plutôt que de simplement coller du code dans la boîte de réponse. Veuillez lire Comment répondre
durron597
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.