wok-api-conversion / src /utils /encryption.py
woook001's picture
Upload 35 files
b30d305 verified
Raw
History Blame Contribute Delete
8.46 kB
"""
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()