【问题标题】:Password masking console application密码屏蔽控制台应用程序
【发布时间】:2011-03-25 04:22:35
【问题描述】:

我尝试了以下代码...

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

但是这样输入密码时退格功能不起作用。 有什么建议吗?

【问题讨论】:

  • 我建议您不要将任何内容回显到控制台,因为这会暴露密码的长度。
  • @RayCheng - 很公平,但很少有用户界面(除了某些 Unix 系统)根本没有回应。为了与其他应用和网站保持一致的用户体验,最好显示 * 字符。
  • @StephenHolt 我相当确定我遇到的每个基于终端的密码输入都选择不向终端回显任何内容。鉴于安全优势以及这是 Unix 世界中众所周知的约定,我个人认为不回显是正确的选择,除非您认为您的用户群可能不熟悉终端的使用(在这种情况下无论如何,最好还是使用 GUI)。

标签: c# passwords console-application user-input masking


【解决方案1】:

Console.Write("\b \b"); 将从屏幕上删除星号字符,但您的 else 块中没有任何代码可以从 pass 字符串变量中删除先前输入的字符。

以下是应满足您要求的相关工作代码:

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

【讨论】:

  • 哦,我以为 \b \b 会带我回到两个位置。尽管如此,这似乎工作正常。
  • @Nadeem:注意退格字符 ('\b') 之间的空格字符 (' ')。 "\b \b" 将您带回一个位置,然后打印一个空格(将您向前带一个位置),然后再次将您带回,因此您最终会回到已删除的 '*' 字符所在的位置。
  • @Nadeem - 第一个 \b 将光标向后移动一个位置(现在在最后一个 * 字符下方。[space] 字符“打印”星号,但也将光标移动一个字符再次向前,所以最后一个 \b 将光标移回最后一个 * 曾经所在的位置!(呼——希望这是有道理的!)
  • if (pass.Length > 0) 应该是if (key.Key == ConsoleKey.Backspace && pass.Length > 0) 否则您将无法获得密码的最后一个字符..
  • 如果您不希望用户能够编写控制字符(如 F5 或 Escape),您可以将 if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) 替换为 if (!char.IsControl(key.KeyChar))
【解决方案2】:

为此,您应该使用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;
}

【讨论】:

  • 我需要将 if( pwd.Length > 0) 嵌套到第一个 else 语句中以阻止人们删除问题:)
  • 与 Safron 对当前接受的答案的评论类似,最终的 else 子句将受益于测试 if (!char.IsControl(i.KeyChar))(或至少 if (i.KeyChar != '\u0000'))。
  • 我怎样才能把那个密码变成一个字符串?
  • @Amessihel 虽然该文档很好地说明了不使用凭据,但如果您必须使用它们,SecureString 总比没有好。
【解决方案3】:

完整的解决方案,原版 C# .net 3.5+

剪切和粘贴 :)

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

【讨论】:

  • 它总是更困难 :) 不能在 Mac/Linux 上工作,因为换行符未被识别。 Environment.NewLine 具有换行符的字符串。所以我将其修改为:while (!Environment.NewLine.Contains(chr = System.Console.ReadKey(true).KeyChar))
【解决方案4】:

取最上面的答案,以及来自它的cmets的建议,并修改它以使用SecureString而不是String,测试所有控制键,当密码长度时不会出错或在屏幕上多写一个“*”为0,我的解决办法是:

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

【讨论】:

    【解决方案5】:

    我的忽略控制字符并处理换行:

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

    【讨论】:

    • 完美运行。您可能需要添加代码以使 DELETE character 删除所有输入的文本。它的键序列是CTRL + BACKSPACE,它的字符代码是0x7f
    【解决方案6】:

    这会用红色方块掩盖密码,然后在输入密码后恢复为原始颜色。

    它不会阻止用户使用复制/粘贴来获取密码,但如果它只是为了阻止别人偷看你的肩膀,这是一个很好的快速解决方案。

    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;
    

    【讨论】:

    • 唯一的问题是,如果你执行退格,你有字符的背景会保持红色。我宁愿将 ForegroundColor 设置为 origBG 以获得 Linux 风格的密码输入。
    • 您也可以使用Console.CursorVisible=false 并将其设置回之前的值。这将防止某人在密码长度上达到峰值。
    【解决方案7】:

    阅读控制台输入很困难,您需要处理特殊键,例如 Ctrl、Alt,以及光标键和 Backspace/Delete。在某些键盘布局上,例如Swedish Ctrl 甚至需要输入直接存在于美国键盘上的键。我相信尝试使用“低级”Console.ReadKey(true) 来处理这个问题非常困难,因此最简单和最可靠的方法是在输入密码期间使用一些 WINAPI 禁用“控制台输入回显”。

    以下示例基于对Read a password from 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;
        }
    

    【讨论】:

      【解决方案8】:

      我在 shermy 的 vanilla C# 3.5 .NET 解决方案中发现了一个错误,否则它很有魅力。我还在这里合并了 Damian Leszczyński - Vash 的 SecureString 想法,但如果您愿意,可以使用普通字符串。

      错误:如果您在密码提示期间按退格键并且密码的当前长度为 0,则星号会错误地插入密码掩码中。要修复此错误,请修改以下方法。

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

      【讨论】:

        【解决方案9】:

        这是一个增加了对Escape 键(返回null 字符串)支持的版本

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

        【讨论】:

          【解决方案10】:

          (我的)nuget package 这样做,基于最佳答案:

          install-package PanoramicData.ConsoleExtensions

          用法:

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

          项目网址:https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

          欢迎拉取请求。

          【讨论】:

          • 我将它用于一个小项目。按预期工作。谢谢
          【解决方案11】:

          您可以将您的密钥附加到一个累积的链表中。

          收到退格键后,从列表中删除最后一个键。

          收到回车键后,将列表折叠成一个字符串,然后完成剩下的工作。

          【讨论】:

          • 听起来可以,但我将如何从显示中删除最后一个字符。
          【解决方案12】:

          我对退格做了一些更改

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

          【讨论】:

            【解决方案13】:

            我已经更新了 Ronnie 的版本,因为我花了 太多时间 尝试输入密码,却发现我的 CAPS LOCK 已打开!

            在此版本中,_CapsLockMessage 中的任何消息都将“浮动”在输入区域的末尾并以红色显示。

            此版本需要更多代码,并且确实需要轮询循环。在我的计算机上,CPU 使用率约为 3% 到 4%,但如果需要,可以随时添加一个小的 Sleep() 值来降低 CPU 使用率。

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

            【讨论】:

              【解决方案14】:

              这是我的简单版本。 每次按下一个键,从控制台中删除所有内容,并绘制与密码字符串长度一样多的“*”。

              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
              

              【讨论】:

                【解决方案15】:

                天哪

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

                【讨论】:

                • 嗯,退格等呢...?
                【解决方案16】:

                如果我理解正确,您是在尝试让退格键同时删除屏幕上可见的 * 字符和 pass 变量中的缓存字符?

                如果是这样,那么只需将您的 else 块更改为:

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

                【讨论】:

                • 这样可以正常工作,只是不会显示退格删除的字符。
                【解决方案17】:
                 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);
                

                【讨论】:

                • 这个答案没有添加任何超出现有答案的内容。此外,好的答案通常应该解释代码,而不仅仅是将代码粘贴到答案框中。请阅读How to Answer
                猜你喜欢
                • 2011-04-09
                • 2013-12-18
                • 2011-12-29
                • 2011-11-17
                • 2021-09-27
                • 1970-01-01
                • 1970-01-01
                • 2014-02-23
                相关资源
                最近更新 更多