#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 加密工具模块 - 精简版 用于加密存储敏感信息(如第三方账号密码) 使用Fernet对称加密 """ import os import base64 from pathlib import Path from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC def _get_key_paths(): """获取密钥文件路径""" from config import ENCRYPTION_KEY_FILE, ENCRYPTION_SALT_FILE return ENCRYPTION_KEY_FILE, ENCRYPTION_SALT_FILE def _get_or_create_salt() -> bytes: """获取或创建盐值""" _, salt_path = _get_key_paths() if salt_path.exists(): with open(salt_path, 'rb') as f: return f.read() # 生成新的盐值 salt = os.urandom(16) salt_path.parent.mkdir(parents=True, exist_ok=True) with open(salt_path, 'wb') as f: f.write(salt) return salt def _derive_key(password: bytes, salt: bytes) -> bytes: """从密码派生加密密钥""" kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, # OWASP推荐的迭代次数 ) return base64.urlsafe_b64encode(kdf.derive(password)) def get_encryption_key() -> bytes: """获取加密密钥(优先环境变量,否则从文件读取或生成)""" key_path, _ = _get_key_paths() # 优先从环境变量读取 env_key = os.environ.get('ENCRYPTION_KEY') if env_key: salt = _get_or_create_salt() return _derive_key(env_key.encode(), salt) # 从文件读取 if key_path.exists(): with open(key_path, 'rb') as f: return f.read() # 生成新的密钥 key = Fernet.generate_key() key_path.parent.mkdir(parents=True, exist_ok=True) with open(key_path, 'wb') as f: f.write(key) print(f"[OK] 已生成新的加密密钥") return key # 全局Fernet实例 _fernet = None def _get_fernet() -> Fernet: """获取Fernet加密器(懒加载)""" global _fernet if _fernet is None: key = get_encryption_key() _fernet = Fernet(key) return _fernet def encrypt_password(plain_password: str) -> str: """ 加密密码 Args: plain_password: 明文密码 Returns: str: 加密后的密码(base64编码) """ if not plain_password: return '' fernet = _get_fernet() encrypted = fernet.encrypt(plain_password.encode('utf-8')) return encrypted.decode('utf-8') def decrypt_password(encrypted_password: str) -> str: """ 解密密码 Args: encrypted_password: 加密的密码 Returns: str: 明文密码 """ if not encrypted_password: return '' try: fernet = _get_fernet() decrypted = fernet.decrypt(encrypted_password.encode('utf-8')) return decrypted.decode('utf-8') except Exception as e: # 解密失败,可能是旧的明文密码 print(f"[Warning] 密码解密失败,可能是未加密的旧数据: {e}") return encrypted_password def is_encrypted(password: str) -> bool: """ 检查密码是否已加密 Fernet加密的数据以'gAAAAA'开头 Args: password: 要检查的密码 Returns: bool: 是否已加密 """ if not password: return False return password.startswith('gAAAAA') def migrate_password(password: str) -> str: """ 迁移密码:如果是明文则加密,如果已加密则保持不变 Args: password: 密码(可能是明文或已加密) Returns: str: 加密后的密码 """ if is_encrypted(password): return password return encrypt_password(password)