Подтвердить что ты не робот

Как выполнить ввод пароля в 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() не может обрабатывать умлауты и другие типы символов, по крайней мере до некоторой степени.

Чтобы прийти к решению, необходимо решить следующие проблемы:

  • Как можно считывать из stdin символ по символу в отношении символов, отличных от ASCII?
  • Как это можно сделать независимым от платформы способом?
  • Как это можно сделать безопасно (без проблем безопасности)? (getpass как-то, кажется, обращается к этому, но я не совсем понимаю, как.)

Возможно, об этом никто не знает. Насколько мне известно, такие вопросы, которые были заданы ранее, остались без ответа. Но, может быть, в сообществе есть несколько разработчиков программного обеспечения, которые знают больше обо всем этом и могут помочь здесь? При успехе я могу получить решение, предоставленное на GitHub в качестве модуля python.

4b9b3361

Ответ 2

Вы можете посмотреть, как это реализовано в jupyter/ipython. Я получаю точку, отображаемую немедленно для каждого символа, набранного с помощью метода getpass().

введите описание изображения здесь

Ответ 3

ПРИМЕЧАНИЕ. Мой другой ответ содержит рабочий код 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).

Ответ 4

Это версия только для 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

Ответ 5

вы можете найти этот рецепт полезным как отправная точка для своего собственного независимого от платформы решения
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'

Ответ 6

Я написал модуль, чтобы проиллюстрировать, как вы делаете эту платформу самостоятельно.

#!/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())

Ответ 7

Возможно, проще использовать 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()

Извините, я больше не мог помочь.