PYTHON
generator.py🐍python
"""
Password generation utilities.
"""
import secrets
import string
from typing import List
# Character sets
LOWERCASE = string.ascii_lowercase
UPPERCASE = string.ascii_uppercase
DIGITS = string.digits
SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?"
AMBIGUOUS = "l1I0O" # Characters that look similar
def generate_password(
length: int = 16,
lowercase: bool = True,
uppercase: bool = True,
digits: bool = True,
symbols: bool = True,
exclude_ambiguous: bool = False,
exclude_chars: str = ""
) -> str:
"""
Generate a cryptographically secure random password.
Args:
length: Password length (minimum 4)
lowercase: Include lowercase letters
uppercase: Include uppercase letters
digits: Include digits
symbols: Include special characters
exclude_ambiguous: Exclude similar-looking characters
exclude_chars: Additional characters to exclude
Returns:
Random password string
Raises:
ValueError: If no character types enabled or length too short
"""
if length < 4:
raise ValueError("Password length must be at least 4")
# Build character pool
pool = ""
required = [] # Ensure at least one of each type
if lowercase:
chars = LOWERCASE
if exclude_ambiguous:
chars = ''.join(c for c in chars if c not in AMBIGUOUS)
pool += chars
required.append(chars)
if uppercase:
chars = UPPERCASE
if exclude_ambiguous:
chars = ''.join(c for c in chars if c not in AMBIGUOUS)
pool += chars
required.append(chars)
if digits:
chars = DIGITS
if exclude_ambiguous:
chars = ''.join(c for c in chars if c not in AMBIGUOUS)
pool += chars
required.append(chars)
if symbols:
pool += SYMBOLS
required.append(SYMBOLS)
# Remove excluded characters
if exclude_chars:
pool = ''.join(c for c in pool if c not in exclude_chars)
if not pool:
raise ValueError("No characters available for password generation")
# Generate password ensuring at least one of each required type
password = []
# Add one of each required type
for chars in required:
valid_chars = ''.join(c for c in chars if c not in exclude_chars)
if valid_chars:
password.append(secrets.choice(valid_chars))
# Fill remaining length
remaining = length - len(password)
password.extend(secrets.choice(pool) for _ in range(remaining))
# Shuffle to randomize positions
password_list = list(password)
secrets.SystemRandom().shuffle(password_list)
return ''.join(password_list)
def generate_passphrase(
words: int = 4,
separator: str = "-",
capitalize: bool = False,
include_number: bool = False
) -> str:
"""
Generate a random passphrase using common words.
Args:
words: Number of words
separator: Word separator
capitalize: Capitalize first letter of each word
include_number: Add a random number
Returns:
Random passphrase
"""
# Common words (subset of EFF word list)
wordlist = [
"apple", "banana", "cherry", "dragon", "eagle", "falcon", "guitar",
"hammer", "island", "jungle", "kitten", "lemon", "marble", "needle",
"orange", "pencil", "quartz", "rabbit", "silver", "tiger", "umbrella",
"violet", "wallet", "yellow", "zebra", "anchor", "beacon", "castle",
"desert", "empire", "forest", "galaxy", "harbor", "impact", "jacket",
"knight", "ladder", "magnet", "nectar", "oasis", "palace", "quantum",
"rhythm", "sunset", "temple", "upward", "velvet", "window", "zodiac",
"arctic", "bronze", "cosmic", "domain", "engine", "frozen", "global",
"hollow", "ignore", "joyful", "kernel", "legend", "mellow", "noble",
"octave", "planet", "rocket", "shadow", "throne", "unique", "voyage"
]
# Select random words
selected = [secrets.choice(wordlist) for _ in range(words)]
# Apply transformations
if capitalize:
selected = [w.capitalize() for w in selected]
passphrase = separator.join(selected)
if include_number:
passphrase += separator + str(secrets.randbelow(1000))
return passphrase
def calculate_password_strength(password: str) -> dict:
"""
Calculate password strength score.
Args:
password: Password to analyze
Returns:
Dictionary with score and details
"""
score = 0
feedback = []
length = len(password)
# Length scoring
if length < 8:
feedback.append("Password is too short (minimum 8 characters)")
elif length < 12:
score += 20
feedback.append("Consider using a longer password")
elif length < 16:
score += 30
else:
score += 40
# Character variety
has_lower = any(c in LOWERCASE for c in password)
has_upper = any(c in UPPERCASE for c in password)
has_digit = any(c in DIGITS for c in password)
has_symbol = any(c in SYMBOLS for c in password)
variety_count = sum([has_lower, has_upper, has_digit, has_symbol])
if variety_count == 1:
feedback.append("Add different character types")
elif variety_count == 2:
score += 15
feedback.append("Consider adding more character types")
elif variety_count == 3:
score += 25
else:
score += 35
# Common patterns to avoid
common_patterns = [
"123", "abc", "qwerty", "password", "letmein",
"111", "000", "aaa", "admin", "login"
]
password_lower = password.lower()
for pattern in common_patterns:
if pattern in password_lower:
score -= 20
feedback.append(f"Avoid common pattern: '{pattern}'")
break
# Sequential characters
sequential = 0
for i in range(len(password) - 1):
if ord(password[i+1]) == ord(password[i]) + 1:
sequential += 1
if sequential >= 3:
score -= 10
feedback.append("Avoid sequential characters")
# Repeated characters
repeated = 0
for i in range(len(password) - 1):
if password[i] == password[i+1]:
repeated += 1
if repeated >= 3:
score -= 10
feedback.append("Avoid repeated characters")
# Ensure score is in range
score = max(0, min(100, score))
# Determine strength level
if score < 40:
level = "Weak"
elif score < 60:
level = "Fair"
elif score < 80:
level = "Good"
else:
level = "Strong"
return {
"score": score,
"level": level,
"feedback": feedback,
"length": length,
"has_lowercase": has_lower,
"has_uppercase": has_upper,
"has_digits": has_digit,
"has_symbols": has_symbol
}
def check_password_breach(password: str) -> bool:
"""
Check if password appears in known data breaches.
Uses k-anonymity model (only first 5 chars of hash sent).
Note: This is a placeholder - real implementation would
use the HaveIBeenPwned API.
Args:
password: Password to check
Returns:
True if password found in breaches
"""
import hashlib
# In a real implementation, you would:
# 1. SHA1 hash the password
# 2. Send first 5 characters to HIBP API
# 3. Check if remaining hash is in response
# Placeholder - always returns False
return False