web-dev-qa-db-ja.com

python)でパスワード入力を行うには、ユーザーのすべての文字にアスタリスクを印刷するにはどうすればよいですか?

問題:プログラマーはユーザーにパスワードの入力を求めます。 getpass()関数はこの目的に適していますが、その使用には欠点があります。パスワードを入力している間、stdout

質問:ユーザーが入力したすべての文字に対してアスタリスクが出力されている間に、getpass()をどのように実装できますか? (もちろんbackspace-理想的にはpos1end-とるべきですそれに応じて注意してください。)

動機:なぜこの質問がなされたのかを理解していないコミュニティの人々がいます。次に、getpass()をa)参照して、a)この方法で手元のタスクを無視し、b)参照が質問に答えないことを考えずにs.o.の理由ユーザーの便宜のために、アスタリスクを印刷することをお勧めします。パスワードの入力中に直接視覚的に応答します。そのため、キーを押しても混乱することはなく、目には何も起こっていないようです。

ソリューションへのステップ:

ここで解決策への最初のステップを紹介しましょう。それを実際のソリューションに進化させるために助けてください。

gdinという名前のモジュールがあり、stdinから1文字ずつ読み取ることができるようです。バックスペースは-奇妙なことに-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

しかし、このコードにはいくつかの欠点があります。最初に、s.oの場合、cが '\ b'にならないことについて非常に混乱しています。 backspaceを入力しました。多分s.o.これについて説明がありますか? 2番目のみASCII文字は少なくともLinuxでは処理されます。ここではWindowsについては知りませんが、A-Z0-9以外の文字が押された場合、行c = getch.getch()は例外をスローします。getch()はウムラウトや他の種類の文字を処理できないようです。少なくともある程度は。

ソリューションの入力を行うには、次の問題に対処する必要があります。

  • stdinからの読み取りは、ASCII以外の文字に関して文字ごとにどのように実行できますか?
  • これは、プラットフォームに依存しない方法でどのように実行できますか?
  • これを安全に行うにはどうすればよいですか(セキュリティの問題はありません)。 (getpassどういうわけかこれに対処しているようですが、私はその方法を完全に理解していません。)
22
Regis May

そこで最初の答えを見てください:

python端末からキーボード入力を検出する最も簡単な方法は何ですか?

キーが押されたときに星印「*」または何かを印刷するだけです。

すべての信用は明らかに研究のためにフィリダに向けられています。

5
Loïc

私は、プラットフォームを個別にどのように実行するかを大まかに説明するためにモジュールを書きました。

_#!/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
_

そして、これはシングルバイトエンコーディングで機能します。ユニコードのサポートを追加することは簡単ではありません。 Windowsでgetpassモジュールを使用すると、Unicodeがうまく機能しないと思います。 (注:すべてをユニコード文字列に変更してgetwch()を使用するほど簡単ではありません)

1
nabin-info

注:私の他の答えには、プラットフォームに依存しない方法でこれを行うためのpython2コードが含まれています

安全なプラットフォームに依存しない方法では、getpass.getpass()と同じものがすべてセットアップされるので、ソース(_/usr/lib/python2.7/getpass.py_の場合)を確認してください。とても簡単です。

星の響きは......

win_getpass()はすでに1文字ずつ読み込んでいます。そのループで_*_をエコーし​​てください。 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の場合)。

1
nabin-info

これはLinuxのみのバージョンであり、Python 2およびPython 3 with nicodeサポートで動作します。

Unicode文字を入力するには、Ctrl+Shiftを同時に入力し、uと入力してリリースCtrl+Shift、コードポイントを入力して<Enter>

os.readおよびos.write(libcおよびpython IO)バッファリングの問題を回避し、カーネルからバイトを読み取るための関数。

ターミナルKILL(^ U)、ERASE(ascii DEL別名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
1
Nizam Mohamed

あなたはjupyter/ipythonがこれをどのように実装したかを見たいかもしれません。 getpass()を使用して入力したすべての文字に対して、すぐにドットが表示されます。

enter image description here

1
denfromufa

このレシピは、プラットフォームに依存しない独自のソリューションの出発点として役立つかもしれません
http://code.activestate.com/recipes/134892/

Windowsの場合、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'
0
Alexey Smirnov