The getpass module provides secure password input. Characters aren't echoed to the terminal—essential for any CLI that handles credentials.
Basic Usage
Prompt for password without echo:
import getpass
password = getpass.getpass()
print(f"You entered {len(password)} characters")The default prompt is "Password: " and nothing appears as you type.
Custom Prompt
import getpass
password = getpass.getpass(prompt='Enter your API key: ')
secret = getpass.getpass(prompt='Database password: ')Get Username
import getpass
# Get current system username
username = getpass.getuser()
print(f"Logged in as: {username}")Login Flow
Common pattern for CLI authentication:
import getpass
def login():
"""Prompt for credentials."""
username = input('Username: ')
password = getpass.getpass('Password: ')
return username, password
def authenticate(username, password):
"""Verify credentials (example)."""
# In reality, check against database/API
return username == 'admin' and password == 'secret'
username, password = login()
if authenticate(username, password):
print('Login successful!')
else:
print('Invalid credentials')CLI Tool Example
#!/usr/bin/env python3
import getpass
import argparse
def main():
parser = argparse.ArgumentParser(description='Database CLI')
parser.add_argument('--user', '-u', help='Username')
parser.add_argument('--password', '-p', action='store_true',
help='Prompt for password')
args = parser.parse_args()
username = args.user or input('Username: ')
if args.password:
password = getpass.getpass()
else:
password = None
connect_to_database(username, password)
if __name__ == '__main__':
main()Confirm Password
Double-entry for registration:
import getpass
def get_new_password():
"""Prompt for new password with confirmation."""
while True:
password = getpass.getpass('New password: ')
confirm = getpass.getpass('Confirm password: ')
if password == confirm:
return password
print("Passwords don't match. Try again.")
password = get_new_password()Password Strength Check
import getpass
import re
def is_strong_password(password):
"""Check password meets requirements."""
if len(password) < 8:
return False, "Must be at least 8 characters"
if not re.search(r'[A-Z]', password):
return False, "Must contain uppercase letter"
if not re.search(r'[a-z]', password):
return False, "Must contain lowercase letter"
if not re.search(r'\d', password):
return False, "Must contain digit"
return True, "OK"
def get_strong_password():
"""Prompt until strong password entered."""
while True:
password = getpass.getpass('Password: ')
valid, message = is_strong_password(password)
if valid:
return password
print(f"Weak password: {message}")API Key Input
import getpass
def configure_api():
"""Prompt for API credentials."""
print("Enter your API credentials (input is hidden)")
api_key = getpass.getpass('API Key: ')
api_secret = getpass.getpass('API Secret: ')
return {
'api_key': api_key,
'api_secret': api_secret
}Environment Variable Fallback
import getpass
import os
def get_password():
"""Get password from env or prompt."""
password = os.environ.get('APP_PASSWORD')
if password:
return password
return getpass.getpass('Password: ')SSH-Style Input
import getpass
def ssh_login(host):
"""SSH-style login prompt."""
username = getpass.getuser() # Default to current user
user_input = input(f'{username}@{host}\'s password: ')
if user_input:
username = user_input
password = getpass.getpass(f'{username}@{host}\'s password: ')
return username, passwordHandling Non-TTY
When stdin isn't a terminal (piped input):
import getpass
import sys
def safe_getpass(prompt='Password: '):
"""Handle both TTY and non-TTY input."""
if sys.stdin.isatty():
return getpass.getpass(prompt)
else:
# Piped input - read line (less secure)
print(prompt, end='', file=sys.stderr)
return sys.stdin.readline().rstrip('\n')Timeout Pattern
import getpass
import signal
class TimeoutError(Exception):
pass
def timeout_handler(signum, frame):
raise TimeoutError("Input timed out")
def getpass_with_timeout(prompt='Password: ', timeout=30):
"""Password input with timeout."""
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout)
try:
password = getpass.getpass(prompt)
signal.alarm(0) # Cancel alarm
return password
except TimeoutError:
print("\nTimeout - no password entered")
return NoneSecurity Notes
getpass does:
- Disable terminal echo
- Read from /dev/tty when available
- Provide basic protection from shoulder surfing
getpass doesn't:
- Encrypt the password in memory
- Protect against keyloggers
- Clear password from memory after use
Clearing Sensitive Data
import getpass
import ctypes
def secure_input():
"""Get password and attempt to clear from memory."""
password = getpass.getpass()
# Use the password
result = authenticate(password)
# Attempt to clear (not guaranteed in Python)
password_bytes = password.encode()
ctypes.memset(id(password_bytes), 0, len(password_bytes))
del password
return resultNote: Python's string immutability makes true secure clearing difficult.
Custom Stream
Write prompt to different stream:
import getpass
import sys
# Write prompt to stderr (useful when stdout is redirected)
password = getpass.getpass(prompt='Password: ', stream=sys.stderr)When to Use getpass
Use getpass when:
- CLI tools need password input
- Interactive credential prompts
- Any secret that shouldn't be visible
Don't use when:
- GUI applications (use proper password fields)
- Non-interactive scripts (use env vars or config)
- Input is piped (check
sys.stdin.isatty())
Simple, focused, essential for secure CLI tools.
React to this post: