비밀번호 마스킹 콘솔 애플리케이션


201

다음 코드를 시도했습니다 ...

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

그러나 암호를 입력하는 동안 백 스페이스 기능이 작동하지 않습니다. 어떠한 제안?


11
암호 길이가 노출되므로 콘솔에 아무것도 다시 표시하지 않는 것이 좋습니다.
Ray Cheng

8
@RayCheng-충분히 공평하지만 유닉스 시스템 이외의 사용자 인터페이스는 거의 없습니다. 다른 앱 및 웹 사이트에서 일관된 사용자 경험을 얻으려면 * 문자를 표시하는 것이 가장 좋습니다.
Stephen Holt

4
@StephenHolt 나는 내가 본 모든 터미널 기반 암호 입력이 터미널에 아무것도 에코하지 않기로 결정했다고 확신합니다. 보안상의 이점과 이것이 유닉스 세계에서 잘 알려진 관례라는 사실을 감안할 때, 사용자 기반이 터미널 사용에 익숙하지 않다고 생각하지 않는 한 개인적으로 에코하는 것은 올바른 선택이 아니라고 생각합니다 (이 경우 어쨌든 GUI를 사용하는 것이 가장 좋습니다.)
Ajedi32

답변:


225

Console.Write("\b \b");화면에서 별표 문자를 삭제하지만 else블록 내에 는 이전에 입력 한 문자를 pass문자열 변수 에서 제거하는 코드가 없습니다 .

필요한 작업을 수행하는 관련 작업 코드는 다음과 같습니다.

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
오, 나는 \ b \ b 나를 두 곳으로 데려 갈 것이라고 생각했다. 그럼에도 불구하고 이것은 제대로 작동하는 것 같습니다.
Mohammad Nadeem

8
@Nadeem : ' '백 스페이스 문자 ( '\b') 사이에 공백 문자 ( )가 있습니다. "\b \b"한 자리 뒤로 이동 한 다음 공백을 인쇄 한 다음 (한 자리 앞으로 이동) 다시 뒤로 이동하여 삭제 된 '*'문자가 있는 위치로 돌아갑니다 .
dtb

14
@Nadeem-첫 번째 \b는 커서를 한 위치 뒤로 이동시킵니다 (이제 마지막 *문자 아래에 있습니다. [space]문자는 별표를 "인쇄"하지만 커서는 한 문자 앞으로 다시 \b이동 하므로 마지막 은 커서를 마지막으로 *사용한 위치로 다시 이동시킵니다. (Phew-이해가
되길 바랍니다

3
if (pass.Length > 0)해야하는 것은 if (key.Key == ConsoleKey.Backspace && pass.Length > 0)그렇지 않으면 당신은 암호의 마지막 문자를받지 않습니다 ..
MemphiZ

9
사용자가 제어 문자 (예 : F5 또는 Escape)를 쓰지 못하게하려면으로 바꿀 수 if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)있습니다 if (!char.IsControl(key.KeyChar)).
Safron

90

이를 위해 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;
}

이것은 단지 두 곳만 데려다 줄 것입니다. 그러나 내가 필요한 것은 백 스페이스를 누르면 마지막 문자가 삭제되어야한다는 것입니다. 백 스페이스의 원래 기능과 같습니다.
Mohammad Nadeem

1
if( pwd.Length > 0)사람들이 질문을 삭제하는 것을 막기 위해 첫 번째 else 문 에 중첩해야했습니다. :)
Dead.Rabit

1
현재 허용되는 답변에 대한 Safron의 의견과 마찬가지로 최종 else조항은 테스트 if (!char.IsControl(i.KeyChar))(또는 최소한 if (i.KeyChar != '\u0000'))의 이점을 얻습니다 .
피터 테일러

1
암호를 문자열로 바꾸려면 어떻게해야합니까?
Joseph Kreifels II


47

완벽한 솔루션, 바닐라 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('*');
            }
        }
    }

3
항상 더 어려울 수 있습니다 :) 줄 바꿈이 인식되지 않기 때문에 Mac / Linux에서는 작동하지 않습니다. Environment.NewLine에는 개행을위한 문자열이 있습니다. 그래서 이것을 다음과 같이 수정했습니다. while (! Environment.NewLine.Contains (chr = System.Console.ReadKey (true) .KeyChar))
Hugo Logmans

18

주석의 제안뿐만 아니라 최상위 답변을 취하고 String 대신 SecureString을 사용하도록 수정하고 모든 제어 키를 테스트하고 암호 길이가 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;
}

15

광산은 제어 문자를 무시하고 줄 바꿈을 처리합니다.

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 문자가 지우 도록 코드를 추가해야 할 수도 있습니다 . 키 순서는 CTRL + BACKSPACE이고 문자 코드는 0x7f입니다.
Alex Essilfie 2016 년

9

콘솔 입력을 읽기가 어렵 기 때문에 Ctrl, Alt, 커서 키 및 백 스페이스 / 삭제와 같은 특수 키를 처리해야합니다. 스웨덴어 키보드와 같은 일부 키보드 레이아웃에서는 미국 키보드에 직접 존재하는 키를 입력해야합니다. 나는 "낮은 수준"을 사용하여 이것을 처리하려고 노력Console.ReadKey(true) 매우 어렵다고 생각하므로 가장 쉽고 강력한 방법은 약간의 WINAPI를 사용하여 암호를 입력하는 동안 "콘솔 입력 에코"를 비활성화하는 것입니다.

아래 샘플 은 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

이렇게하면 비밀번호가 빨간색 정사각형으로 마스킹 된 다음 비밀번호를 입력하면 원래 색상으로 되돌아갑니다.

사용자가 암호를 얻기 위해 복사 / 붙여 넣기를 사용하는 것을 막지는 않지만 어깨 너머로 누군가를 보는 것을 막는 것이 좋은 방법입니다.

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 스타일 암호 입력을 사용하려고합니다.
mababin

1
Console.CursorVisible=false나중에 수행 하고 이전 값으로 다시 설정할 수도 있습니다 . 이것은 누군가가 암호 길이에 정점에 도달하지 못하게합니다.
mababin

6

shermy의 바닐라 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;
    }

4

다음은 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;
        }
    }
}

2

(내) 너겟 패키지는 최고 답변을 기반으로합니다.

install-package PanoramicData.ConsoleExtensions

용법:

using PanoramicData.ConsoleExtensions;

...

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

프로젝트 URL : https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

풀 요청을 환영합니다.


나는 이것을 작은 프로젝트에 사용했습니다. 예상대로 작동했습니다. 감사합니다
gmalenko

1

누적 된 연결 목록에 키를 추가 할 수 있습니다.

백 스페이스 키가 수신되면 목록에서 마지막 키를 제거하십시오.

Enter 키를 받으면 목록을 문자열로 축소하고 나머지 작업을 수행하십시오.


달성 할 수있는 소리이지만 디스플레이에서 마지막 문자를 어떻게 제거합니까?
Mohammad Nadeem

1

백 스페이스를 변경했습니다.

        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

여기 내 간단한 버전이 있습니다. 키를 누를 때마다 콘솔에서 모두 삭제하고 암호 문자열 길이만큼 '*'를 그립니다.

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

비밀번호를 입력하는 데 너무 많은 시간 을 투자 하여 CAPS LOCK이 켜져 있음을 알기 위해 Ronnie의 버전을 업데이트 했습니다!

이 버전에서는 메시지가 _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();
    }

-1

이것을 올바르게 이해하면 백 스페이스가 화면의 보이는 * 문자와 패스 변수의 캐시 된 문자를 모두 삭제하려고합니까?

그렇다면 else 블록을 다음과 같이 변경하십시오.

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

1
백 스페이스에 의한 문자 삭제가 표시되지 않는 것을 제외하고는 잘 작동합니다.
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
이 답변은 기존 답변 이상의 것을 추가하지 않습니다. 또한 좋은 답변은 일반적으로 답변 상자에 코드를 붙여 넣기보다는 코드를 설명해야합니다. 답변 방법
durron597
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.