Ответ 1
Посмотрите на первый ответ:
Каков самый простой способ обнаружения ввода клавиатуры в python с терминала?
Просто нажмите звездочки '*' или что угодно, когда нажата клавиша.
Весь кредит, очевидно, относится к Филлии для исследования.
Этот вопрос задан раньше и до сих пор не имеет ответа, насколько я могу судить. Поэтому я снова задаю этот вопрос, чтобы, наконец, получить ответ на этот вопрос. (Если я ошибаюсь, дайте мне URL-адрес ответа.)
Проблема: Программисты хотят, чтобы пользователи вводили пароли. Функция getpass() хороша для этой цели, но ее использование имеет недостаток: при вводе пароля ничего не печатается в стандартный вывод.
Вопрос: Как можно реализовать getpass(), пока звездочки печатаются для каждого символа, введенного пользователем? (Конечно, обратное пространство - и идеально pos1 и конец - следует позаботиться соответственно.)
Мотивация: В сообществе были люди, которые не понимали, почему этот вопрос задан. И затем ссылается на getpass(): a) таким образом игнорируя поставленную задачу и б) не думая о том, что ссылка не ответит на вопрос. Причина, по которой s.o. может потребоваться напечатать звездочки для удобства пользователей: они получают прямой визуальный ответ во время ввода пароля. Поэтому они не путаются, нажимая клавиши и - там глаза - ничего не происходит.
Шаг к решению:
Позвольте мне представить первый шаг к решению здесь. Пожалуйста, помогите, чтобы превратить его в реальное решение.
Существует модуль с именем getch, который, по-видимому, позволяет читать символ по символу из stdin. Backspace - довольно странно - отображается в целочисленное значение 127, но такое решение может выглядеть следующим образом:
def readLineWithAsterisks():
sBuffer = ''
while True:
c = getch.getch()
if c == '\n':
return sBuffer
elif ord(c) == 127:
if len(sBuffer) > 0:
sys.stdout.write('\x08 \x08')
sys.stdout.flush()
sBuffer = sBuffer[0:-1]
continue
else:
sys.stdout.write('*')
sys.stdout.flush()
sBuffer += c
Но этот код имеет некоторые недостатки. Сначала я очень смущен тем, что c не был '\ b', если s.o. вступил в обратное пространство. Может быть и так. есть объяснение для этого? Во-вторых обрабатываются только ASCII-символы, по крайней мере, в Linux. Я не знаю о Windows здесь, но если нажимается символ, отличный от A-Z0-9, строка c = getch.getch() выдает исключение. Кажется, что getch() не может обрабатывать умлауты и другие типы символов, по крайней мере до некоторой степени.
Чтобы прийти к решению, необходимо решить следующие проблемы:
Возможно, об этом никто не знает. Насколько мне известно, такие вопросы, которые были заданы ранее, остались без ответа. Но, может быть, в сообществе есть несколько разработчиков программного обеспечения, которые знают больше обо всем этом и могут помочь здесь? При успехе я могу получить решение, предоставленное на GitHub в качестве модуля python.
Посмотрите на первый ответ:
Каков самый простой способ обнаружения ввода клавиатуры в python с терминала?
Просто нажмите звездочки '*' или что угодно, когда нажата клавиша.
Весь кредит, очевидно, относится к Филлии для исследования.
Вы можете посмотреть, как это реализовано в jupyter/ipython. Я получаю точку, отображаемую немедленно для каждого символа, набранного с помощью метода getpass().
ПРИМЕЧАНИЕ. Мой другой ответ содержит рабочий код python2, чтобы сделать это независимым от платформы способом.
Безопасный независимый от платформы способ устанавливает все, что идентично getpass.getpass()
, поэтому посмотрите на источник (/usr/lib/python2.7/getpass.py
для меня); это довольно прямолинейно.
Что касается эхо-сигналов звезд...
win_getpass()
уже считывает char с помощью char, просто эхо-сигнал *
в этом цикле. Возможно, вам придется использовать msvcrt.getwch()
вместо msvcrt.getch()
, но это означает, что у модуля python getpass
есть ошибка.
unix_getpass()
сложнее. Вам нужно настроить cbreak
для терминала, аналогичного тому, как ECHO
уже отключен (см. https://utcc.utoronto.ca/~cks/space/blog/unix/CBreakAndRaw). Затем вам придется использовать read(1)
в цикле (аналогично win_getpass()
), а не readline()
, который использует _raw_input()
.
Как только вы читаете байт за байтом, вы можете пройти через то, что нужно определить, что составляет "письмо". Это зависит от кодировки и может даже быть переменной по длине (в случае UTF-8).
Это версия только для Linux, работает в Python 2 и Python 3 с поддержкой Unicode.
Чтобы ввести символы Юникода, удерживайте Ctrl+Shift
одновременно и введите u
и отпустите Ctrl+Shift
, теперь введите код и <Enter>
.
Я исключительно использую os.read
и os.write
funcitons для обхода (libc и python IO) буферизации и чтения байтов из ядра.
Поддерживаются терминал KILL (^ U), ERASE (ascii DEL aka Backspace
), EOF (^ D) и ascii BS (\b
).
Я игнорирую SIGTSTP
при чтении пароля, потому что при повторном возврате из фона повторяются символы.
import tty
import os
import sys
import signal
from array import array
# disable (^Z) SIGTSTP
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
stdin = sys.__stdin__.fileno()
stream = sys.__stderr__.fileno()
old = tty.tcgetattr(stdin)
os.write(stream, b"Passwd: ")
try:
tty.setcbreak(stdin)
passwd = array("u")
while True:
# UTF-8 is 4 octets (bytes) at max
c = os.read(stdin, 4)
# ERASE ascii DEL (0x7f) <Backspace> and ascii BS (0x08) <^H>
if c in (old[tty.CC][tty.VERASE], b"\b"):
if passwd:
os.write(stream, b"\b \b")
passwd.pop()
# KILL ascii NAK (0x15) <^U>
elif c == old[tty.CC][tty.VKILL]:
if passwd:
os.write(stream, b"\b \b" * len(passwd))
passwd = array("u")
# ascii LF (0x0a) <^J>, CR (0x0d) <^M> and <Enter> and EOT (0x04) <^D>
elif c in (b"\n", old[tty.CC][tty.VEOF]):
break
else:
#c = c.decode('utf-8')
c = c.decode(sys.__stdin__.encoding)
passwd.append(c)
os.write(stream, b"*")
finally:
# restore terminal settings
tty.tcsetattr(stdin, tty.TCSAFLUSH, old)
# enable (^Z) SIGTSTP
signal.signal(signal.SIGTSTP, signal.SIG_DFL)
os.write(stream, b"\n")
print(passwd.tounicode())
Тест
$ # To input "Þàsswõrd"
$ # U+00de, U+00e0, s,s, w, U+00f5, r, d
$ python getpass.py
$ Passwd: ********
Þàsswõrd
вы можете найти этот рецепт полезным как отправная точка для своего собственного независимого от платформы решения
http://code.activestate.com/recipes/134892/
В случае с окнами используется msvcrt lib. Вы можете заменить вызов getch() на getwch(), чтобы иметь возможность обрабатывать unicode.
ActivePython 2.7.10.12 (ActiveState Software Inc.) based on
Python 2.7.10 (default, Aug 21 2015, 12:07:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import msvcrt
>>> msvcrt.getch()
'j'
>>> msvcrt.getch()
'r'
>>> msvcrt.getch()
'?'
>>> msvcrt.getwch()
u'\u0432'
Я написал модуль, чтобы проиллюстрировать, как вы делаете эту платформу самостоятельно.
#!/usr/bin/python2
def _masked_input_unix(prompt="Password: ", mask="*"):
pw = ""
# save terminal settings
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
new = termios.tcgetattr(fd)
# setup 'cbreak' mode
new[3] = new[3] & ~termios.ECHO
new[3] = new[3] & ~termios.ICANON
new[6][termios.VMIN] = '\x01'
new[6][termios.VTIME] = '\x00'
try:
termios.tcsetattr(fd, termios.TCSADRAIN, new)
print prompt,
# Read the password
while True:
c = sys.stdin.read(1)
# submit chars
if c == '\r' or c == '\n':
sys.stdout.write("%s" % (c))
break
# delete chars
elif c == '\b' or c == '\x7f':
if len(pw) > 0:
pw = pw[:-1]
sys.stdout.write("%s" % ('\b \b'))
# password chars
else:
pw += c
sys.stdout.write("%s" % (mask))
finally:
# ensure we reset the terminal
termios.tcsetattr(fd, termios.TCSADRAIN, old)
return pw
def _masked_input_win(prompt="Password: ", mask='*'):
pw = ""
while True:
c = msvcrt.getch()
# submit chars
if c == '\r' or c == '\n':
while msvcrt.kbhit():
msvcrt.getch()
print
break
elif c == '\x03':
raise KeyboardInterrupt
# delete chars
elif c == '\b' or c == '\x7f':
if len(pw) > 0:
pw = pw[:-1]
msvcrt.putch('\b')
msvcrt.putch(' ')
msvcrt.putch('\b')
# password chars
else:
pw += c
msvcrt.putch(mask)
return pw
## initialize windows or posix function pointer
masked_input = None
try:
import msvcrt
masked_input = _masked_input_win
except ImportError:
import sys, termios
masked_input = _masked_input_unix
if __name__ == "__main__":
p = masked_input()
print "Password is:", p
И это работает для однобайтовых кодировок. Добавление поддержки unicode является нетривиальным. Я подозреваю, что unicode не работает с модулем getpass
в Windows. (ПРИМЕЧАНИЕ: это не так просто, как изменение всего на строки unicode и использование getwch()
)
Возможно, проще использовать tkinter, хотя, вероятно, не так безопасно, это самое близкое, что я мог бы получить от того, что вы просите.
from tkinter import *
from tkinter import ttk
root = Tk()
parent = ttk.Frame(root)
parent.grid()
password = ttk.Entry(parent, show="*").grid()#shows each character as an asterix
root.mainloop()
Извините, я больше не мог помочь.