Python’s getpass module reads passwords from the terminal with echo suppressed and retrieves the current username. import getpass. Password prompt: password = getpass.getpass(prompt="Password: ", stream=None) — disables terminal echo, reads one line, restores echo; stream defaults to the controlling tty or sys.stderr (not stdout, to avoid contaminating piped output). Username: getpass.getuser() → string — checks LOGNAME, USER, LNAME, USERNAME environment variables in order, then falls back to pwd.getpwuid(os.getuid()).pw_name on Unix. Warning: getpass.GetPassWarning — subclass of UserWarning emitted when the terminal doesn’t support echo suppression (piped stdin, some CI environments, IDEs); in those cases the password is still read but will be visible. Platform notes: on Windows uses msvcrt.getwch(); on Unix uses termios; always reads from /dev/tty if available to bypass stdin redirection. Claude Code generates terminal authentication prompts, interactive credential collectors, sudo-style privilege escalation helpers, and CLI login flows.
CLAUDE.md for getpass
## getpass Stack
- Stdlib: import getpass
- Password: pw = getpass.getpass("Password: ") # echo disabled
- pw = getpass.getpass(stream=sys.stderr) # explicit stream
- User: name = getpass.getuser() # current login name
- Warning: GetPassWarning — echo may be visible (pipe/IDE)
- Note: Never log or serialize the returned string; hash it immediately
getpass Secure Password Input Pipeline
# app/getpassutil.py — prompt, hash, verify, retry, sudo-style privilege check
from __future__ import annotations
import getpass
import hashlib
import hmac
import os
import secrets
import sys
import warnings
from dataclasses import dataclass
# ─────────────────────────────────────────────────────────────────────────────
# 1. Secure prompt helpers
# ─────────────────────────────────────────────────────────────────────────────
def prompt_password(
prompt: str = "Password: ",
confirm: bool = False,
confirm_prompt: str = "Confirm password: ",
stream=None,
) -> str:
"""
Prompt for a password with optional confirmation.
Returns the entered password string.
Raises ValueError if confirmation doesn't match.
Example:
pw = prompt_password("New password: ", confirm=True)
"""
with warnings.catch_warnings():
warnings.simplefilter("ignore", getpass.GetPassWarning)
pw = getpass.getpass(prompt, stream=stream)
if confirm:
pw2 = getpass.getpass(confirm_prompt, stream=stream)
if pw != pw2:
raise ValueError("Passwords do not match")
return pw
def prompt_credentials(
username_prompt: str = "Username: ",
password_prompt: str = "Password: ",
) -> tuple[str, str]:
"""
Prompt for username (visible) and password (hidden).
Returns (username, password).
Example:
user, pw = prompt_credentials()
"""
username = input(username_prompt).strip()
password = prompt_password(password_prompt)
return username, password
# ─────────────────────────────────────────────────────────────────────────────
# 2. Password hashing (for storage)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class HashedPassword:
"""
PBKDF2-HMAC-SHA256 hashed password suitable for storage.
"""
algorithm: str # "pbkdf2_sha256"
iterations: int
salt_hex: str # 32 hex chars = 16 bytes
hash_hex: str # 64 hex chars = 32 bytes
def to_string(self) -> str:
"""Return portable string: algorithm$iterations$salt$hash"""
return f"{self.algorithm}${self.iterations}${self.salt_hex}${self.hash_hex}"
@classmethod
def from_string(cls, s: str) -> "HashedPassword":
parts = s.split("$")
if len(parts) != 4:
raise ValueError(f"Invalid hashed password format: {s!r}")
return cls(
algorithm=parts[0],
iterations=int(parts[1]),
salt_hex=parts[2],
hash_hex=parts[3],
)
def hash_password(
password: str,
iterations: int = 260_000,
) -> HashedPassword:
"""
Hash a plaintext password using PBKDF2-HMAC-SHA256.
Returns a HashedPassword for storage.
Example:
hashed = hash_password("s3cr3t")
stored = hashed.to_string()
# later:
if verify_password("s3cr3t", HashedPassword.from_string(stored)):
print("authenticated")
"""
salt = secrets.token_bytes(16)
dk = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
iterations,
dklen=32,
)
return HashedPassword(
algorithm="pbkdf2_sha256",
iterations=iterations,
salt_hex=salt.hex(),
hash_hex=dk.hex(),
)
def verify_password(password: str, hashed: HashedPassword) -> bool:
"""
Verify a plaintext password against a HashedPassword.
Uses constant-time comparison to prevent timing attacks.
Example:
ok = verify_password(entered_pw, stored_hashed)
"""
salt = bytes.fromhex(hashed.salt_hex)
dk = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
hashed.iterations,
dklen=32,
)
return hmac.compare_digest(dk.hex(), hashed.hash_hex)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Retry logic
# ─────────────────────────────────────────────────────────────────────────────
def authenticate_with_retry(
verify_fn: "callable[[str, str], bool]",
max_attempts: int = 3,
username: str | None = None,
) -> bool:
"""
Prompt for credentials up to max_attempts times, calling verify_fn(user, pw).
Returns True on success, False after exhausting attempts.
Example:
PASSWORD_DB = {"alice": hash_password("s3cr3t")}
def check(user, pw):
h = PASSWORD_DB.get(user)
return h is not None and verify_password(pw, h)
if authenticate_with_retry(check):
print("Welcome!")
else:
print("Too many failed attempts")
"""
for attempt in range(1, max_attempts + 1):
try:
if username is None:
user = input("Username: ").strip()
else:
user = username
pw = prompt_password()
except (EOFError, KeyboardInterrupt):
print()
return False
if verify_fn(user, pw):
return True
remaining = max_attempts - attempt
if remaining > 0:
print(f"Incorrect. {remaining} attempt{'s' if remaining != 1 else ''} remaining.",
file=sys.stderr)
else:
print("Authentication failed.", file=sys.stderr)
return False
# ─────────────────────────────────────────────────────────────────────────────
# 4. Privilege escalation helper (sudo-style)
# ─────────────────────────────────────────────────────────────────────────────
def current_user() -> str:
"""
Return the login name of the current process owner.
Uses getpass.getuser() which checks env vars then pwd database.
Example:
print(f"Running as: {current_user()}")
"""
return getpass.getuser()
def is_root() -> bool:
"""Return True if the current process is running as root (uid 0) on Unix."""
return os.getuid() == 0 if hasattr(os, "getuid") else False
@dataclass
class ElevationResult:
authorized: bool
user: str
message: str
def __str__(self) -> str:
status = "AUTHORIZED" if self.authorized else "DENIED"
return f"[{status}] {self.user}: {self.message}"
def require_password_confirmation(
prompt: str = "Confirm your password to continue: ",
stored_hash_str: str | None = None,
) -> ElevationResult:
"""
Ask the user to confirm their own password before a sensitive operation.
If stored_hash_str is provided, verifies against it.
If None, always returns authorized=True after entry (trust-on-entry mode).
Example:
result = require_password_confirmation(
stored_hash_str=my_stored_hash.to_string()
)
if not result.authorized:
sys.exit(1)
"""
user = current_user()
try:
pw = prompt_password(prompt)
except (EOFError, KeyboardInterrupt):
print()
return ElevationResult(False, user, "Cancelled by user")
if stored_hash_str is None:
return ElevationResult(True, user, "Password accepted (trust mode)")
hashed = HashedPassword.from_string(stored_hash_str)
if verify_password(pw, hashed):
return ElevationResult(True, user, "Password verified")
return ElevationResult(False, user, "Incorrect password")
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== getpass demo ===")
# ── current_user ──────────────────────────────────────────────────────────
print(f"\n--- current_user ---")
print(f" getpass.getuser(): {getpass.getuser()!r}")
print(f" is_root(): {is_root()}")
# ── hash_password / verify_password ───────────────────────────────────────
print(f"\n--- hash_password / verify_password ---")
hashed = hash_password("correct_horse_battery_staple", iterations=10_000)
stored = hashed.to_string()
print(f" stored: {stored[:60]}...")
print(f" verify correct: {verify_password('correct_horse_battery_staple', hashed)}")
print(f" verify incorrect: {verify_password('wrong_password', hashed)}")
# ── HashedPassword round-trip ─────────────────────────────────────────────
print(f"\n--- HashedPassword round-trip ---")
restored = HashedPassword.from_string(stored)
print(f" algorithm={restored.algorithm} iterations={restored.iterations}")
print(f" restored verify: {verify_password('correct_horse_battery_staple', restored)}")
# ── Non-interactive prompt simulation ─────────────────────────────────────
print(f"\n--- getpass.getpass in non-interactive context ---")
print(" (skipping live prompt in demo — use in interactive terminal)")
print(" pw = getpass.getpass('Password: ') # echo disabled")
print(" user, pw = prompt_credentials() # username + password")
# ── GetPassWarning example ────────────────────────────────────────────────
print(f"\n--- GetPassWarning detection ---")
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# In a redirected environment, getpass emits GetPassWarning
# We simulate the warning detection pattern:
had_warning = any(issubclass(x.category, getpass.GetPassWarning) for x in w)
print(f" GetPassWarning caught: {had_warning}")
print("\n=== done ===")
For the pwinput (PyPI) alternative — pwinput.pwinput(prompt, mask="*") shows masking characters as the user types (asterisks or any char) rather than complete silence — use pwinput when visual feedback that keystrokes are being received is important for UX; use getpass.getpass (showing nothing) for highest security where even asterisk count revelation is undesirable, or in headless/automated environments where pwinput would behave unhelpfully. For the keyring (PyPI) alternative — keyring.get_password(service, username) and keyring.set_password(service, username, password) store and retrieve credentials in the OS native secret store (macOS Keychain, Windows Credential Locker, libsecret on Linux) — use keyring for applications that need to persist credentials between runs so the user doesn’t have to retype; use getpass for one-shot interactive prompts where credentials should not be stored on disk, or for first-time password input before hashing and storing a derived key. The Claude Skills 360 bundle includes getpass skill sets covering prompt_password()/prompt_credentials() with echo suppression and confirmation, HashedPassword with hash_password()/verify_password() PBKDF2-HMAC-SHA256 storage, authenticate_with_retry() multi-attempt login loop, and ElevationResult with require_password_confirmation() sudo-style privilege helper. Start with the free tier to try secure input patterns and getpass pipeline code generation.