Sunday, July 15, 2007

PyMOTW: getpass

Module: getpass
Purpose: Prompt the user for a value, usually a password, without echoing what they type to the console.
Python Version: 1.5.2

Description:

Many programs which interact with the user via the terminal need to ask the user for password values without showing what the user types on the screen. The getpass module provides a portable way to handle such password prompts securely.

Example:

The getpass() function prints a prompt then reads input from the user until they press return. The input is passed back as a string to the caller.

import getpass

p = getpass.getpass()
print 'You entered:', p


The default prompt, if none is specified by the caller, is "Password:".

$ python getpass_defaults.py
Password:
You entered: sekret


Of course the prompt can be anything your program needs.

p = getpass.getpass(prompt='What is your favorite color? ')
if p.lower() == 'blue':
print 'Right. Off you go.'
else:
print 'Auuuuugh!'


I don't recommend such an insecure authentication scheme, but it illustrates the point.

 $ python getpass_prompt.py
What is your favorite color?
Right. Off you go.
$ python getpass_prompt.py
What is your favorite color?
Auuuuugh!


By default, getpass() uses stdout to print the prompt string. For a program which may produce useful output on sys.stdout, it is useful to send the prompt to another stream such as sys.stderr.

import getpass
import sys

p = getpass.getpass(stream=sys.stderr)
print 'You entered:', p


This way standard output can be redirected (to a pipe or file) without seeing the password prompt. The value entered by the user is still not echoed back to the screen.

$ python getpass_stream.py >/dev/null
Password:


Using getpass Without a Terminal

Under Unix, getpass() always requires a tty it can control via termios, so echo can be disabled. This means values will not be read from a non-terminal stream redirected to standard input.

$ echo "sekret" | python getpass_defaults.py
Traceback (most recent call last):
File "getpass_defaults.py", line 34, in
p = getpass.getpass()
File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/getpass.py", line 32, in unix_getpass
old = termios.tcgetattr(fd) # a copy to save
termios.error: (25, 'Inappropriate ioctl for device')


It is up to the caller to detect when the input stream is not a tty and use an alternate method for reading in that case.

import getpass
import os
import sys

if os.isatty(sys.stdin.fileno()):
p = getpass.getpass('Using getpass: ')
else:
print 'Using readline'
p = sys.stdin.readline().rstrip()

print 'Read: ', p


With a tty:

$ python ./getpass_noterminal.py
Using getpass:
Read: sekret


Without a tty:

$ echo "sekret" | python ./getpass_noterminal.py
Using readline
Read: sekret


References:

Python Module of the Week Home
Download Sample Code


Technorati Tags:
,


3 comments:

toidinamai said...

I think "os.isatty(sys.stdin.fileno())" could be written more nicely as "sys.stdin.isatty()".

Doug said...

You're absolutely right. Thanks for the tip!

frikk said...

Hey man,
Your snippit regarding the os.isatty is perfect. Thanks! Turns out Eclipse's debugging console is not treated as a TTY when using getpass.getpass(). This works perfectly, thanks again!