""" 密碼產生與驗證工具 """ import secrets import string import re import bcrypt def generate_secure_password(length: int = 16) -> str: """ 產生安全的隨機密碼 Args: length: 密碼長度(預設 16 字元) Returns: 安全的隨機密碼 範例: >>> pwd = generate_secure_password() >>> len(pwd) 16 >>> validate_password_strength(pwd) True """ if length < 8: raise ValueError("密碼長度至少需要 8 個字元") # 字元集合 lowercase = string.ascii_lowercase uppercase = string.ascii_uppercase digits = string.digits special = "!@#$%^&*()-_=+[]{}|;:,.<>?" # 確保至少包含每種類型各一個 password = [ secrets.choice(lowercase), secrets.choice(uppercase), secrets.choice(digits), secrets.choice(special) ] # 剩餘字元隨機選擇 all_chars = lowercase + uppercase + digits + special password += [secrets.choice(all_chars) for _ in range(length - 4)] # 打亂順序 secrets.SystemRandom().shuffle(password) return ''.join(password) def hash_password(password: str) -> str: """ 使用 bcrypt 加密密碼 Args: password: 明文密碼 Returns: 加密後的密碼 hash """ password_bytes = password.encode('utf-8') salt = bcrypt.gensalt() hashed = bcrypt.hashpw(password_bytes, salt) return hashed.decode('utf-8') def verify_password(plain_password: str, hashed_password: str) -> bool: """ 驗證密碼 Args: plain_password: 明文密碼 hashed_password: 加密密碼 Returns: 是否匹配 """ return bcrypt.checkpw( plain_password.encode('utf-8'), hashed_password.encode('utf-8') ) def validate_password_strength(password: str) -> tuple[bool, list[str]]: """ 驗證密碼強度 Args: password: 待驗證的密碼 Returns: (是否通過, 錯誤訊息列表) 範例: >>> validate_password_strength("weak") (False, ['密碼長度至少需要 8 個字元', ...]) >>> validate_password_strength("Strong@Pass123") (True, []) """ errors = [] # 長度檢查 if len(password) < 8: errors.append("密碼長度至少需要 8 個字元") # 大寫字母 if not re.search(r'[A-Z]', password): errors.append("密碼必須包含至少一個大寫字母") # 小寫字母 if not re.search(r'[a-z]', password): errors.append("密碼必須包含至少一個小寫字母") # 數字 if not re.search(r'\d', password): errors.append("密碼必須包含至少一個數字") # 特殊符號 if not re.search(r'[!@#$%^&*()\-_=+\[\]{}|;:,.<>?]', password): errors.append("密碼必須包含至少一個特殊符號") # 常見弱密碼檢查 common_weak_passwords = [ 'password', 'password123', '12345678', 'qwerty', 'admin123', 'letmein', 'welcome', 'monkey' ] if password.lower() in common_weak_passwords: errors.append("此密碼過於常見,請使用更安全的密碼") return (len(errors) == 0, errors) def validate_password_for_user( password: str, username: str = None, name: str = None, email: str = None ) -> tuple[bool, list[str]]: """ 驗證密碼(包含使用者資訊檢查) Args: password: 待驗證的密碼 username: 使用者帳號 name: 使用者姓名 email: Email Returns: (是否通過, 錯誤訊息列表) """ # 先檢查基本強度 is_valid, errors = validate_password_strength(password) # 檢查是否包含使用者資訊 password_lower = password.lower() if username and username.lower() in password_lower: errors.append("密碼不可包含帳號名稱") if name: name_parts = name.split() for part in name_parts: if len(part) >= 3 and part.lower() in password_lower: errors.append("密碼不可包含姓名") break if email: email_user = email.split('@')[0] if len(email_user) >= 3 and email_user.lower() in password_lower: errors.append("密碼不可包含 Email 使用者名稱") return (len(errors) == 0, errors) if __name__ == "__main__": # 測試密碼產生 print("=== 密碼產生測試 ===") for i in range(5): pwd = generate_secure_password() is_valid, errors = validate_password_strength(pwd) print(f"密碼 {i+1}: {pwd} - 有效: {is_valid}") # 測試密碼驗證 print("\n=== 密碼驗證測試 ===") test_cases = [ ("weak", False), ("WeakPass", False), ("WeakPass123", False), ("Strong@Pass123", True), ("admin@Pass123", True) ] for password, expected in test_cases: is_valid, errors = validate_password_strength(password) status = "✓" if is_valid == expected else "✗" print(f"{status} {password}: {is_valid}") if errors: for error in errors: print(f" - {error}") # 測試加密與驗證 print("\n=== 密碼加密測試 ===") plain_pwd = "TestPassword@123" hashed = hash_password(plain_pwd) print(f"明文密碼: {plain_pwd}") print(f"加密密碼: {hashed}") print(f"驗證正確密碼: {verify_password(plain_pwd, hashed)}") print(f"驗證錯誤密碼: {verify_password('WrongPassword', hashed)}")