""" 密码哈希工具类 使用 bcrypt 安全存储密码 """ from typing import Optional import bcrypt class PasswordHasher: """密码哈希服务""" def __init__(self, rounds: int = 12): """ 初始化密码哈希器 Args: rounds: bcrypt cost factor(默认 12,推荐范围 10-14) 值越大越安全,但计算时间越长 """ if rounds < 4 or rounds > 31: raise ValueError("rounds must be between 4 and 31") self.rounds = rounds def hash_password(self, password: str) -> str: """ 哈希密码 Args: password: 明文密码 Returns: bcrypt 哈希字符串(包含 salt) Raises: ValueError: 密码为空 """ if not password: raise ValueError("Password cannot be empty") # bcrypt 需要 bytes password_bytes = password.encode("utf-8") # 生成 salt 并哈希 salt = bcrypt.gensalt(rounds=self.rounds) hashed = bcrypt.hashpw(password_bytes, salt) # 返回字符串(数据库存储) return hashed.decode("utf-8") def verify_password(self, password: str, hashed_password: str) -> bool: """ 验证密码 Args: password: 明文密码 hashed_password: 存储的哈希密码 Returns: True 如果密码正确,否则 False """ if not password or not hashed_password: return False try: password_bytes = password.encode("utf-8") hashed_bytes = hashed_password.encode("utf-8") return bcrypt.checkpw(password_bytes, hashed_bytes) except Exception: # 哈希格式错误或其他异常,返回 False return False def needs_rehash(self, hashed_password: str) -> bool: """ 检查哈希是否需要重新计算 (当 cost factor 改变时需要重新哈希) Args: hashed_password: 存储的哈希密码 Returns: True 如果需要重新哈希 """ try: hashed_bytes = hashed_password.encode("utf-8") current_rounds = bcrypt.getsalt(hashed_bytes) # 提取当前的 cost factor # bcrypt hash 格式: $2b$rounds$salt+hash parts = hashed_password.split("$") if len(parts) >= 3: stored_rounds = int(parts[2]) return stored_rounds != self.rounds return False except Exception: return False class PasswordValidator: """密码强度验证器""" def __init__( self, min_length: int = 8, require_uppercase: bool = True, require_lowercase: bool = True, require_digit: bool = True, require_special: bool = False, ): """ 初始化密码验证器 Args: min_length: 最小长度 require_uppercase: 是否要求大写字母 require_lowercase: 是否要求小写字母 require_digit: 是否要求数字 require_special: 是否要求特殊字符 """ self.min_length = min_length self.require_uppercase = require_uppercase self.require_lowercase = require_lowercase self.require_digit = require_digit self.require_special = require_special def validate(self, password: str) -> tuple[bool, Optional[str]]: """ 验证密码强度 Args: password: 明文密码 Returns: (是否有效, 错误信息) """ if not password: return False, "Password cannot be empty" if len(password) < self.min_length: return False, f"Password must be at least {self.min_length} characters" if self.require_uppercase and not any(c.isupper() for c in password): return False, "Password must contain at least one uppercase letter" if self.require_lowercase and not any(c.islower() for c in password): return False, "Password must contain at least one lowercase letter" if self.require_digit and not any(c.isdigit() for c in password): return False, "Password must contain at least one digit" if self.require_special: special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?~" if not any(c in special_chars for c in password): return False, "Password must contain at least one special character" return True, None # 全局实例 password_hasher = PasswordHasher(rounds=12) password_validator = PasswordValidator( min_length=8, require_uppercase=True, require_lowercase=True, require_digit=True, require_special=False, )