主要功能: - 账号管理:添加/编辑/删除账号,测试登录 - 浏览任务:批量浏览应读/选读内容并标记已读 - 截图管理:wkhtmltoimage截图,查看历史 - 金山文档:扫码登录/微信快捷登录,自动上传截图 技术栈: - PyQt6 GUI框架 - Playwright 浏览器自动化 - SQLite 本地数据存储 - wkhtmltoimage 网页截图 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
157 lines
3.7 KiB
Python
157 lines
3.7 KiB
Python
#!/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)
|