2016-11-17 596 views
19

就我所见,此问题之前已被询问过,但仍然没有答案。所以我再次问这个问题,以便最终得到这个问题的答案。 (如果我错了,请给我答案的网址。)如何在python中输入密码并为用户的每个角色打印一个星号?

问题:程序员希望用户输入密码。 getpass()函数很好用于此目的,但它的使用有一个缺点:在输入密码时,没有任何内容被打印到stdout

问题:如何才能将getpass()被星号的同时打印由用户键入的每一个字符来实现? (当然退格 - 甚至是POS1年底 - 应采取照顾相应。)

的动机:已经有社会人士不明白为什么这个问题已经被问。然后用以下方法提及getpass() a)通过这种方式忽略手头的任务和b)不考虑那里的引用不会回答问题。 s.o.的原因可能希望打印星号以方便用户:在密码输入期间,他们会得到直接的视觉响应。因此,他们不会因按下按键而感到困惑,而且对于眼睛 - 似乎没有任何事情发生。

向溶液的步骤:

让我介绍这里走向一个解决方案的第一步。请帮助将其发展成真正的解决方案。

有一个名为残培这似乎允许从标准输入通过字符读取字符模块。退格键是 - 很奇怪 - 映射到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如果s.o不是'\ b'。输入退格键。也许是这样。有这个解释吗?第二个只有ASCII字符被处理,至少在Linux上。我不知道Windows在这里,但如果按下A-Z0-9以外的字符,则行c = getch.getch()将引发异常。 getch()似乎无法处理元音变音和其他类型的字符,至少在某种程度上。

来解决输入以下问题应该得到解决:

  • 如何从标准输入由字符对于非ASCII字符进行字符?
  • 这怎么能以平台独立的方式完成?
  • 这是如何安全地完成的(没有安全问题)?(将getpass某种程度上似乎为解决这一点,但我不完全了解。)

也许没有人知道这件事。据我所知,此前一直被问到的问题仍未得到解答。但是也许社区中有一些软件开发人员对此有了更多的了解,并且可以在这里提供帮助?成功之后,我可以将GitHub上提供的解决方案作为python模块。

回答

-1

它可能更容易使用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() 

对不起,我忍不住了。

0

你会发现这个配方为起点,为自己的平台独立的解决方案
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' 
1

注意:我的其他答案包含工作python2代码以平台无关的方式做到这一点。

安全平台独立的方式将设置一切相同的getpass.getpass()所以看看源(/usr/lib/python2.7/getpass.py为我);它非常直截了当。

至于呼应星星......

win_getpass()被烧焦已经阅读字符,只不过是回应一些*在循环。您可能需要使用msvcrt.getwch()而不是msvcrt.getch(),但这意味着python getpass模块有一个错误。

unix_getpass()更棘手。您需要为终端设置cbreak,类似于ECHO已被禁用(请参阅https://utcc.utoronto.ca/~cks/space/blog/unix/CBreakAndRaw)。然后,您必须在循环中使用read(1)(类似于win_getpass()),而不是使用_raw_input()正在使用的readline()

一旦你逐字节阅读,你可以经历确定什么构成“字母”的痛苦。这取决于编码,甚至可能是长度可变的(在UTF-8的情况下)。

+0

“这是很直接的” - 不,不是真的:-)对不起,但不是每个人都知道的TTY和他们所有的特殊性每一个细节:-) - 和read(1)是没有多大用处:据我了解,python中的STDIN是逐行处理的。这是基本问题之一。不过,我会看看你在这里提供的其他信息。有些似乎很有用。非常感谢你! –

+0

STDIN的linewise处理不是一个基本的python问题; TTY传统上是线路缓冲的,以允许线路编辑在客户端执行。另外,读取(1)在需要读取单个字节时非常有用。 –

1

你可能想看看jupyter/ipython如何实现这个。对于使用getpass()输入的每个字符,我都立即得到一个点。

enter image description here

+0

谢谢,我会仔细研究一下。 –

+0

我没有得到点,而我使用上面的蟒蛇键入我的密码... –

+0

@ nabin-info你的设置有问题 - 我使用的是jupyter 4.2.3。我用gif动画更新了我的答案。 – denfromufa

0

我写给你如何独立做平台模块大致说明。

#!/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与Windows上的getpass模块不兼容。(注:这是不是改变一切以Unicode字符串和使用getwch()一样简单)

1

这是仅限于Linux的版本,适用于Python 2和Python 3,支持Unicode

要输入Unicode字符,请同时保存Ctrl+Shift并键入u和版本Ctrl+Shift,现在键入codepoint并输入<Enter>

我专门使用os.reados.write函数来绕过(libc和python IO)缓冲问题并从内核中读取字节。

支持终端杀戮(^ U),删除(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 
相关问题