""" API密钥加密工具 使用AES加密确保数据库中API密钥的安全性 """ import os import base64 import secrets from typing import Optional, Tuple from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from src.utils.logger import setup_logger from src.utils.env_config import env_config logger = setup_logger("encryption") class APIKeyEncryption: """API密钥加密管理器""" def __init__(self): self._fernet = None self._init_encryption_key() def _init_encryption_key(self): """初始化加密密钥""" # 尝试从环境变量获取加密密钥 encryption_key = os.getenv('ENCRYPTION_KEY') if not encryption_key: # 首先尝试从数据库获取已存储的密钥 encryption_key = self._get_stored_encryption_key() if not encryption_key: # 检查数据库中是否已存在加密数据 has_encrypted_data = self._check_existing_encrypted_data() if has_encrypted_data: logger.error("Found encrypted API keys in database but no ENCRYPTION_KEY available!") logger.error("Option 1: Set ENCRYPTION_KEY in your .env file if you have the key") logger.error("Option 2: Delete encrypted channels and restart to generate new key") raise ValueError("Missing ENCRYPTION_KEY - cannot decrypt existing encrypted data") else: # 没有加密数据,生成新密钥并自动保存到数据库配置 encryption_key = self._generate_encryption_key() self._store_encryption_key(encryption_key) logger.info("Generated new encryption key and stored in database") logger.info("For better security, consider moving this to .env file:") logger.info(f"ENCRYPTION_KEY={encryption_key}") else: logger.info("Using stored encryption key from database") try: # 验证密钥格式 self._fernet = Fernet(encryption_key.encode()) logger.info("Encryption system initialized successfully") except Exception as e: logger.error(f"Failed to initialize encryption: {e}") raise ValueError("Invalid encryption key format") def _check_existing_encrypted_data(self) -> bool: """检查数据库中是否存在加密数据(避免循环导入)""" try: from src.utils.env_config import env_config import sqlite3 db_path = env_config.database_path if not os.path.exists(db_path): return False conn = sqlite3.connect(db_path) cursor = conn.execute( "SELECT COUNT(*) FROM channels WHERE api_key LIKE 'encrypted:%'" ) count = cursor.fetchone()[0] conn.close() return count > 0 except Exception: # 如果查询失败(表不存在等),假设没有加密数据 return False def _generate_encryption_key(self) -> str: """生成新的加密密钥""" # 生成32字节的随机密钥 key = Fernet.generate_key() return key.decode() def _store_encryption_key(self, encryption_key: str): """将加密密钥存储到数据库配置中""" try: from src.utils.env_config import env_config import sqlite3 db_path = env_config.database_path os.makedirs(os.path.dirname(db_path), exist_ok=True) conn = sqlite3.connect(db_path) # 创建配置表(如果不存在) conn.execute(''' CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # 存储加密密钥 conn.execute( 'INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)', ('encryption_key', encryption_key) ) conn.commit() conn.close() except Exception as e: logger.warning(f"Failed to store encryption key in database: {e}") def _get_stored_encryption_key(self) -> Optional[str]: """从数据库配置中获取加密密钥""" try: from src.utils.env_config import env_config import sqlite3 db_path = env_config.database_path if not os.path.exists(db_path): return None conn = sqlite3.connect(db_path) cursor = conn.execute('SELECT value FROM config WHERE key = ?', ('encryption_key',)) result = cursor.fetchone() conn.close() return result[0] if result else None except Exception: return None def encrypt_api_key(self, api_key: str) -> str: """加密API密钥""" if not api_key: return "" try: encrypted_data = self._fernet.encrypt(api_key.encode()) # 返回base64编码的加密数据,添加前缀标识 return f"encrypted:{base64.b64encode(encrypted_data).decode()}" except Exception as e: logger.error(f"Failed to encrypt API key: {e}") raise ValueError("Encryption failed") def decrypt_api_key(self, encrypted_api_key: str) -> str: """解密API密钥""" if not encrypted_api_key: return "" # 检查是否是加密格式 if not encrypted_api_key.startswith("encrypted:"): # 兼容未加密的旧数据 logger.warning("Found unencrypted API key, consider re-saving to encrypt it") return encrypted_api_key try: # 移除前缀并解码 encrypted_data = encrypted_api_key[10:] # 移除 "encrypted:" 前缀 encrypted_bytes = base64.b64decode(encrypted_data.encode()) # 解密 decrypted_data = self._fernet.decrypt(encrypted_bytes) return decrypted_data.decode() except Exception as e: logger.error(f"Failed to decrypt API key: {e}") raise ValueError("Decryption failed - possibly wrong encryption key") def is_encrypted(self, data: str) -> bool: """检查数据是否已加密""" return data.startswith("encrypted:") if data else False def rotate_encryption_key(self, new_key: str, old_encrypted_data: list) -> list: """ 轮换加密密钥(高级功能) 重新加密所有数据使用新密钥 """ # 保存当前密钥 old_fernet = self._fernet try: # 设置新密钥 self._fernet = Fernet(new_key.encode()) # 重新加密所有数据 reencrypted_data = [] for encrypted_item in old_encrypted_data: if self.is_encrypted(encrypted_item): # 使用旧密钥解密 self._fernet = old_fernet decrypted = self.decrypt_api_key(encrypted_item) # 使用新密钥加密 self._fernet = Fernet(new_key.encode()) reencrypted = self.encrypt_api_key(decrypted) reencrypted_data.append(reencrypted) else: reencrypted_data.append(encrypted_item) logger.info(f"Successfully rotated encryption key for {len(reencrypted_data)} items") return reencrypted_data except Exception as e: # 恢复旧密钥 self._fernet = old_fernet logger.error(f"Key rotation failed: {e}") raise # 全局加密管理器实例 encryption_manager = APIKeyEncryption()