본문 바로가기
ОКБ (실험 설계국)/Trouble Shooting

13. [Python] 비밀번호 입력 시, Asterisk(* 별표)가 출력되는 코드 작성

by Rosmary 2021. 8. 7.
728x90
반응형

 

 

 

 

바로 직전에 필자가 작성한 python 포스팅에서, 필자가 만드는 Python 프로그램에 비밀번호 입력 시 * 이 화면에 표시되도록 만드는 코드를 작성하게 된 이유에 대해 서두에 언급했다. 그 내용을 정리하자면, 

 

- 고객사 내부 Windows PC에서 사용될 프로그램이므로, Python이 설치되어 있지 않은 경우를 대비해 실행 파일 형태로 컴파일이 되어야하며,

- 그렇기 때문에 되도록이면 추가 모듈 설치 없이 Python 기본 모듈로만 비밀번호 입력 시 *이 출력되도록 만들어야 한다.

 

라는 것이다. 

 

 

 

 

1. Python의 비밀번호 입력 모듈 getpass의 문제점.

 

Python은 getpass라고 불리는 비밀번호 입력 모듈을 제공한다. getpass모듈 내에는 동일한 이름의 getpass() 함수가 정의되어 있는데, 이 함수는 input()과 사용방법이 동일하다. 차이점이라면 input()은 키보드 입력값이 바로 화면에 출력되는 반면, getpass()는 출력값이 보이지 않는다는 것이다.

 

 

리눅스에 익숙하신 분들이 사용하는 프로그램이라면, 필자도 그냥 getpass로 코드를 작성하여 넘기고 싶지만, 불행히도 필자가 이 프로그램을 전달해야하는 곳은 IT에 익숙한 환경에 있으신 분들이 아니다. 분명 암호를 입력하다가,

 

"이거 암호 입력이 안되는거 같은데요??"

 

라는 전화를 수 백 건 받을 것 같아, 비밀번호 입력 시 무엇이라도 표시가 되도록 만들려고 하는 것이다.

 

따라서 필자는 msvcrt라는 다른 모듈을 사용하여 코드를 작성하려 한다. 

 

 

 

2. msvcrt 모듈

 

msvcrt모듈은 windowsOS의 일부 기능을 구현한 모듈이라고 보면 된다. 보통 키보드 입력과 화면 출력을 제어하는 함수가 대부분이다. 주요 함수(매서드)로는 getch()와 putch()가 있는데, 이들은 입력 또는 출력되는 글자 하나 하나의 값을 Binary 형태로 제어한다. msvcrt로 입출력을 제어하는 것은 크게 어렵지 않으니, 실제 동작이 궁금하신 분들은 이곳에서 확인하도록 하자.

 

 

3. 추가로 필요한 내용: print() 함수 인자의 flush

 

getpass()나 input()을 사용할 수 없기 때문에, 비밀번호를 입력하라는 문구는 print() 함수를 이용하여 출력해야한다. 

 

아래와 같이, input_account라는 함수를 만들어, 이 함수 내에서 ID, 암호를 입력받도록 코드를 작성했다. user_id_input 변수는 ID를 입력받는 부분이므로 입력값을 굳이 숨길 이유가 없으니 input() 함수로 작성하면 된다. 하지만, password_input의 경우, 입력값을 input()이나 getpass()로 받을 수 없기 때문에, "비밀번호를 입력하세요: " 라는 문구를 print() 문으로 작성해주어야 한다. 

 

print()의 end 인자는 print되는 내용을 개행하지 않고 뒤에 붙여쓰겠다는 의미다.

문제는, 위와 같이 작성된 코드를 실행하면, 비밀번호 입력 단계에서 아무리 열심히 키를 눌러봐야 "비밀번호를 입력하세요:" 라는 문구가 출력되지 않는다는 것이다.

 

코드를 조금 수정하여 print() 함수 안의 end="" 인자를 모두 제거한 뒤, 다시 코드를 실행해보자. 그럼 이전과 달리, "비밀번호를 입력하세요:" 라는 문구도 정상적으로 출력됨을 알 수 있다.

 

그런데.... 아무래도 모양새가 이쁘게 나오지는 않는다. 입력되는 모든 문자가 print()문에 의해 강제 개행되다보니, 반드시 end="" 인자가 print() 문에 사용되어야 하는데, 이 인자를 사용하면 문자가 출력이 되지 않는다.

 

print() 함수가 동작하는 방식을 찾아보면 그 이유를 알 수 있다. print("hello")를 예로 들어보자. print() 함수는 문자열을 출력하는 절차를 진행할 때, 마지막에 개행문자(\n 또는 \r)를 붙이게 된다. 개행문자가 작동하기 전까지 hello라는 문자열은 입력 버퍼에 저장되게 되는데, 개행문자가 입력되는 경우 이 입력버퍼에 저장되는 모든 내용이 화면에 출력되는 것이다.

입력버퍼의 내용이 화면에 출력되면 입력버퍼는 깨끗하게 비워진다.

 

그런데, print() 함수에 end="" 인자를 사용하게 되면 print()는 개행문자를 자동으로 사용할 수 없게 되기 때문에 버퍼에 출력하려는 문자열 값이 지속적으로 쌓이게 된다.

 

 

그럼, 저 입력버퍼의 내용을 비밀번호를 입력받기 전에, 그리고 비밀번호를 한 글자씩 입력받을때마다 강제로 화면에 출력하게 할 수만 있다면 이 문제는 해결된다. 다행히 python의 print() 함수는 입력버퍼의 내용을 강제로 화면에 출력하도록 하는 기능을 가지고 있다. 

 

우리가 작성한 print() 문의 인자 가장 마지막에 flush=True를 입력하고 다시 코드를 실행해보자. 

 

 

break 문 앞에 print()로 개행을 한 번 한다면 위와 같이 지저분하게 나오는 일은 없다. 귀찮아서 수정은 못하겠다...

 

즉, end="" 인자를 사용하는 print() 함수의 경우, flush=True 인자의 역할은 강제 개행 문자 입력 및 입력버퍼의 내용을 화면에 출력해주는 것이라 생각하면 된다.  

 

 

 

4. msvcrt.getch(), msvcrt.putch()를 이용한 기능 구현

 

본격적으로 비밀번호 입력 시 * 표시가 입력되도록 코드를 수정해보자. 사실 3번의 내용에서 딱 하나만 수정하면 기본적인 기능은 구현이 된다. 비밀번호 입력 시, 개행문자("\r")만 입력되지 않는다면 모든 내용이 화면에 출력되고 입력버퍼가 비워지도록 print() 함수로 코드가 작성되어 있는데, 이 print() 함수 내 new_char 변수만 "*"로 바꾸어 코드를 실행해보자. 그러면 필자가 원하는 대로 비밀번호 입력 시 * 이 화면에 나타나게 된다.

 

 

 

그러나, 문제가 하나 있다. 만약 비밀번호 입력 시 오타가 나서 백스페이스로 지운다고 가정해보자. 문제는 백스페이스의 경우 개행문자와 달리 별도의 조건문으로 분기가 되어 있지 않다보니, 백스페이스를 입력하면 * 표시가 지워지는 것이 아니라 오히려 하나가 추가된다.

 

13579 입력 후 4 글자를 지우고 다시 2345를 추가한 결과다

 

따라서, if 분기문에서 입력값이 \b인 경우에 대한 조건과 코드를 다음과 같이 추가하면 이 문제는 사라지게 된다.

 

 

!! 추가로 실험을 진행한 결과, 위의 코드에는 에러 사항이 한 가지 있다.

바로 백스페이스 입력 시 진행되는 분기의 마지막 코드인 new_char.decode("utf-8") 이다. Python의 인터프리터 창에서 입력한 암호에 백스페이스가 들어가더라도, 출력에는 아무 이상이 없는 것처럼 보이지만, 실제 입력값이 저장된 변수를 직접 print() 함수로 출력하면 다음과 같이 백스페이스에 대한 내용도 추가되어 보이는 것을 확인할 수 있다.

 

 

의외로 이 문제는 간단하게 해결할 수 있는데, 백스페이스 분기 시 password_input에 백스페이스 디코딩 값을 추가하는 것이 아니라, 바로 이전 단계까지 입력된 값으로 정의해주면 된다. 아래와 같이.

 

 

 

 

5. 기타 문제의 해결

 

(1) 백스페이스의 무한 동작 문제

 

여기까지면 모든 문제가 해결될 것 같지만, 아직 해결되지 않은 문제가 몇 가지 더 있다. 먼저 비밀번호 입력 시, 아무것도 입력하지 말고 백스페이스를 여러 번 입력해보자. 아마 "비밀번호를 입력하세요: "라는 안내문구마저 지워지는 것을 확인할 수 있을 것이다.

 

 

따라서, 백스페이스로 인해 동작하는 조건문의 경우, 지금까지 입력된 문자가 없는 경우 동작하지 않도록 분기를 추가로 설정해주어야 한다. 필자는 password_input 변수에 입력값을 모두 저장하도록 코드를 작성해놓았기 때문에, 다음과 같이 조건문 코드를 조금 수정하여 이 문제를 해결하려 한다.

 

 

 

두 번째 문제는 new_char.decode("utf-8")에서 발생하는 문제다. 비밀번호 입력 시, 화살표 ←↑→↓를 입력하면 에러가 발생하는 것을 볼 수 있다.

 

 

필자가 new_char의 binary 형태 값을 utf-8로 변환시키는 과정에서 발생한 오류인데, 화살표의 경우 utf-8에 상응하는 값이 없어서 저런 에러가 발생하는 것이다. 저 에러는 decode()가 작성된 코드에서 예외처리를 하거나, 아니면 화살표에 상응하는 Binary 값을 if 분기문에 작성하여 아무 동작도 하지 않도록 작성하는 방식으로 해결할 수 있다.

 

 


 

 

지금까지 Python의 기본 모듈을 활용하여 비밀번호 입력 시 * 표시가 출력되도록 코드를 작성해보았다. 사실 이 포스팅에서 작성한 코드의 경우, 필자가 설명을 위해 마구 작성한 코드라 가독성이나 효율은 많이 떨어지는 편이다. 하지만 가독성이 좋거나 나쁜 코드더라도 동작 방식에서는 큰 차이가 없기 때문에 이 포스팅의 코드를 바탕으로 각자가 더 좋은 방향으로 코드를 개선하여 사용하면 좋을 듯 하다. 

 

 

Fin.

반응형

댓글