【原创开源】安全密码管理器 v1.0 - 8 种主题 + 自定义快捷键 + 银行级加密防护

查看 19|回复 0
作者:weiajie   
## 🔐【原创软件】安全密码管理器 v1.0 - 8种主题+自定义快捷键+加密防护系统## 🎉 软件介绍各位坛友好!今天给大家分享一款自己开发的**本地密码管理工具**,完全免费,无需联网,数据100%掌握在自己手中!经过开发和优化,现在发布 **v1.0 正式版**,新增了大量实用功能,界面也更加美观现代化。成品:password_manager.rar - 蓝奏云

快捷键



主界面



查看密码


### 🎨 8种精美主题 - 颜值在线
不再是单调的界面!精心设计了8种主题:
- ☀️ **浅色主题** - 经典白色,适合日间办公
- 🌙 **深色主题** - 护眼深色,长时间使用不累眼  
- 🔵 **蓝色主题** - 商务专业风格
- 🌿 **绿色主题** - 护眼绿色调,程序员最爱
- 💜 **紫色主题** - 优雅时尚,颜值党必选
- 🧡 **橙色主题** - 温暖活力,心情愉悦
- ⚫ **高对比度** - 视力不佳用户福音
- 🌃 **夜间模式** - 深夜使用,减少蓝光**每种主题都经过精心调色,按钮、文字、背景完美搭配!**### ⌨️ 全自定义快捷键 - 效率神器
12个常用功能完全可自定义快捷键:
```
Alt+Q - 添加密码    Alt+W - 查看密码    Alt+E - 编辑密码
Alt+D - 删除密码    Alt+G - 生成密码    Alt+F - 搜索功能
Alt+L - 锁定应用    Alt+I - 导入CSV    Alt+O - 导出CSV
Alt+R - 重置搜索    F5 - 刷新列表      Alt+C - 复制密码
```
- ✅ 智能冲突检测,避免快捷键重复
- ✅ 实时生效,无需重启
- ✅ 永久保存,下次启动自动加载### 🛡️  - 银行级保护
- **AES-GCM 256位加密** - 目前最安全的加密算法之一
- **PBKDF2密钥派生** - 10万次迭代,有效防暴力破解
- **随机盐值保护** - 每个密码独立加密,防彩虹表攻击
- **本地存储** - 数据不上云,100%隐私保护## 🚀 主要功能### 基础功能
- ✅ **密码管理** - 添加/查看/编辑/删除密码记录
- ✅ **强密码生成** - 一键生成高强度密码
- ✅ **智能搜索** - 快速定位目标密码
- ✅ **一键复制** - 密码复制到剪贴板,3秒自动清理
- ✅ **自动锁定** - 15分钟无操作自动锁定保护### 高级功能  
- ✅ **密码强度检测** - 实时检测密码强度,提供改进建议
- ✅ **弱密码扫描** - 一键扫描所有密码,识别安全风险
- ✅ **CSV导入导出** - 方便数据迁移和备份
- ✅ **多重恢复** - 主密码恢复/备份恢复/重建数据库
- ✅ **自动保存配置** - 支持自动登录,提升体验### 界面特色
- ✅ **现代化设计** - 扁平化界面,简洁美观
- ✅ **响应式布局** - 支持窗口缩放,适应不同屏幕
- ✅ **右键菜单** - 丰富的右键操作,提升效率
- ✅ **状态栏显示** - 实时显示记录数量和当前时间- **开发语言**: Python 3.x
- **界面框架**: Tkinter (跨平台)
- **数据库**: SQLite 3 (轻量级)
- **加密库**: cryptography (专业加密库)
- **支持系统**: Windows/macOS/Linux## 🎯 适合人群- 🔸 **个人用户** - 管理各种网站账户密码
- 🔸 **程序员** - 管理开发环境账户信息  
- 🔸 **安全意识强的用户** - 追求本地化存储
- 🔸 **效率控** - 喜欢快捷键操作的用户
- 🔸 **颜值党** - 对界面美观有要求的用户## 📸 界面截图*(这里可以放几张不同主题的截图,展示软件界面)*## 💾 下载使用### 系统要求
- Python 3.6+
- 依赖库:cryptography, pyperclip
- 内存:512MB+
- 存储:50MB+## 🔄 版本更新### v1.0 (最新版)
- 🆕 8种精美主题系统
- 🆕 完全自定义快捷键
- 🆕 密码强度检测和报告
- 🆕 自动锁定保护
- 🆕 CSV导入导出
- 🆕 多重密钥恢复
- 🆕 现代化界面设计### v1.x
- ✅ 基础密码管理
- ✅ AES加密存储
- ✅ 简单界面## 🤝 开源说明本软件完全开源免费,代码公开透明,欢迎大家:
- 🔸 **使用反馈** - 提出使用问题和改进建议
- 🔸 **功能建议** - 希望增加什么新功能
- 🔸 **代码贡献** - 欢迎提交代码改进
- 🔸 **安全审计** - 帮助发现潜在安全问题## 💬 交流讨论如果大家在使用过程中有任何问题或建议,欢迎在本帖回复交流!特别想听听大家对以下方面的意见:
1. 最喜欢哪个主题?为什么?
2. 还希望增加哪些功能?
3. 界面设计有什么改进建议?
4. 使用过程中遇到什么问题?## 🎁 福利感谢坛友们的支持,如果提出有价值的改进建议,我会优先开发实现!---**🔐 让密码管理变得简单而安全!***如果觉得软件不错,请给个赞支持一下!您的支持是我继续开发的动力!* 👍下面展示代码,代码纯开源不上云无任何后门,密码保存的安心![Python] 纯文本查看 复制代码import os
import sqlite3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, filedialog
import hashlib
import base64
import random
import string
import pyperclip
import webbrowser
from datetime import datetime, timedelta
import re
import threading
import time
import re
import csv
import json
class ShortcutManager:
    def __init__(self, db_path):
        self.db_path = db_path
        self.default_shortcuts = {
            'add_password': 'Alt+Q',
            'view_password': 'Alt+W',
            'edit_password': 'Alt+E',
            'delete_password': 'Alt+D',
            'generate_password': 'Alt+G',
            'search': 'Alt+F',
            'lock_app': 'Alt+L',
            'import_csv': 'Alt+I',
            'export_csv': 'Alt+O',
            'reset_search': 'Alt+R',
            'refresh': 'F5',
            'copy_password': 'Alt+C'
        }
        self.current_shortcuts = self.load_shortcuts()
    def load_shortcuts(self):
        """从数据库加载快捷键设置"""
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT name, value FROM settings WHERE name LIKE "shortcut_%"')
            results = c.fetchall()
            conn.close()
            shortcuts = {}
            for name, value in results:
                action = name.replace('shortcut_', '')
                shortcuts[action] = value
            # 合并默认快捷键(如果数据库中没有设置)
            for action, default_key in self.default_shortcuts.items():
                if action not in shortcuts:
                    shortcuts[action] = default_key
            return shortcuts
        except Exception as e:
            print(f"加载快捷键设置失败: {str(e)}")
            return self.default_shortcuts.copy()
    def save_shortcuts(self):
        """保存快捷键设置到数据库"""
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            for action, shortcut in self.current_shortcuts.items():
                setting_name = f'shortcut_{action}'
                c.execute('INSERT OR REPLACE INTO settings (name, value) VALUES (?, ?)',
                         (setting_name, shortcut))
            conn.commit()
            conn.close()
            return True
        except Exception as e:
            print(f"保存快捷键设置失败: {str(e)}")
            return False
    def get_shortcut(self, action):
        """获取指定动作的快捷键"""
        return self.current_shortcuts.get(action, '')
    def set_shortcut(self, action, shortcut):
        """设置指定动作的快捷键"""
        self.current_shortcuts[action] = shortcut
    def reset_to_defaults(self):
        """重置为默认快捷键"""
        self.current_shortcuts = self.default_shortcuts.copy()
    def check_conflict(self, new_shortcut, exclude_action=None):
        """检查快捷键冲突"""
        conflicts = []
        for action, shortcut in self.current_shortcuts.items():
            if action != exclude_action and shortcut == new_shortcut:
                conflicts.append(action)
        return conflicts
    def format_shortcut_display(self, shortcut):
        """格式化快捷键显示"""
        if not shortcut:
            return ""
        return shortcut
    def format_shortcut_binding(self, shortcut):
        """格式化快捷键绑定"""
        if not shortcut:
            return ""
        # 将显示格式转换为Tkinter绑定格式
        binding = shortcut.lower()
        # 处理功能键
        if binding.startswith('f') and binding[1:].isdigit():
            return f''
        # 处理组合键
        parts = binding.split('+')
        if len(parts) == 1:
            # 单个键
            return f''
        # 多个修饰键
        result = ''
        return result
class PasswordStrengthChecker:
    """密码强度检测器"""
    @staticmethod
    def check_strength(password):
        """检查密码强度,返回强度等级和详细信息"""
        if not password:
            return 0, "密码不能为空", "#dc3545"
        score = 0
        feedback = []
        # 长度检查
        if len(password) >= 12:
            score += 25
        elif len(password) >= 8:
            score += 15
            feedback.append("建议密码长度至少12位")
        else:
            feedback.append("密码太短,至少需要8位")
        # 字符类型检查
        has_lower = bool(re.search(r'[a-z]', password))
        has_upper = bool(re.search(r'[A-Z]', password))
        has_digit = bool(re.search(r'\d', password))
        has_special = bool(re.search(r'[!@#$%^&*()_+\-=\[\]{};\':"\\|,.\/?]', password))
        char_types = sum([has_lower, has_upper, has_digit, has_special])
        score += char_types * 15
        if not has_lower:
            feedback.append("缺少小写字母")
        if not has_upper:
            feedback.append("缺少大写字母")
        if not has_digit:
            feedback.append("缺少数字")
        if not has_special:
            feedback.append("缺少特殊字符")
        # 重复字符检查
        if len(set(password)) = 80:
            level = "很强"
            color = "#28a745"
        elif score >= 60:
            level = "强"
            color = "#17a2b8"
        elif score >= 40:
            level = "中等"
            color = "#ffc107"
        elif score >= 20:
            level = "弱"
            color = "#fd7e14"
        else:
            level = "很弱"
            color = "#dc3545"
        return score, level, color, feedback
class AutoLockManager:
    """自动锁定管理器"""
    def __init__(self, timeout_minutes=15):
        self.timeout_minutes = timeout_minutes
        self.last_activity = time.time()
        self.is_locked = False
        self.lock_callback = None
        self.timer_thread = None
        self.running = False
    def start_monitoring(self, lock_callback):
        """开始监控用户活动"""
        self.lock_callback = lock_callback
        self.running = True
        self.timer_thread = threading.Thread(target=self._monitor_activity, daemon=True)
        self.timer_thread.start()
    def stop_monitoring(self):
        """停止监控"""
        self.running = False
    def update_activity(self):
        """更新最后活动时间"""
        self.last_activity = time.time()
        self.is_locked = False
    def _monitor_activity(self):
        """监控活动的后台线程"""
        while self.running:
            if not self.is_locked:
                idle_time = time.time() - self.last_activity
                if idle_time > self.timeout_minutes * 60:
                    self.is_locked = True
                    if self.lock_callback:
                        self.lock_callback()
            time.sleep(30)  # 每30秒检查一次
class PasswordManager:
    def __init__(self, db_path='passwords.db', key_path=None):
        self.db_path = db_path
        # 设置默认密钥文件路径到运行目录
        if key_path is None:
            self.key_path = os.path.join(os.getcwd(), 'secret.key')
        else:
            self.key_path = key_path
        # 设置配置文件路径到运行目录
        self.config_path = os.path.join(os.getcwd(), 'config.dat')
        self.key = None
        self.salt = None
        self.iterations = 100000
        self.master_password_hash = None
        self.is_locked = False
        self.auto_lock_manager = AutoLockManager()
        self.strength_checker = PasswordStrengthChecker()
        self.shortcut_manager = ShortcutManager(self.db_path)
        self.setup()
    def setup(self):
        # 检查数据库和密钥文件状态
        db_exists = os.path.exists(self.db_path)
        key_exists = os.path.exists(self.key_path)
        self.create_database()
        # 清理数据库中过时的加密参数
        conn = sqlite3.connect(self.db_path)
        c = conn.cursor()
        c.execute('DELETE FROM settings WHERE name IN (?, ?)', ('encryption_salt', 'encryption_iterations'))
        conn.commit()
        conn.close()
        # 检查各种情况
        if db_exists and not key_exists:
            # 数据库存在但密钥文件丢失
            self.handle_missing_key_file()
            return False  # 返回False表示初始化未完成
        # 检查是否有保存的配置
        if self.load_saved_config():
            # 配置已存在,直接使用
            messagebox.showinfo('欢迎', '自动登录成功!')
            return True
        if not db_exists:
            # 全新安装
            self.temp_master_password = self.set_master_password()
            self.generate_key()
            # 使用主密码哈希派生密钥
            self.load_key_with_hash(self.master_password_hash)
            self.save_config(self.temp_master_password)
            del self.temp_master_password
            return True
        else:
            # 数据库存在,进行正常验证
            self.verify_master_password()
            return True
    def save_config(self, master_password):
        """保存配置到运行目录(加密存储)"""
        try:
            # 生成配置数据
            config_data = {
                'master_password_hash': hashlib.sha256(master_password.encode()).hexdigest(),
                'salt': base64.b64encode(self.salt).decode(),
                'iterations': self.iterations,
                'created_time': datetime.now().isoformat()
            }
            # 使用机器ID加密配置数据
            machine_id = self._get_machine_id()
            config_json = json.dumps(config_data)
            encrypted_config = self._encrypt_config(config_json, machine_id)
            with open(self.config_path, 'wb') as f:
                f.write(encrypted_config)
            messagebox.showinfo('信息', '配置已安全保存到运行目录,下次启动将自动登录')
        except Exception as e:
            messagebox.showerror('错误', f'保存配置失败: {str(e)}')
    def _encrypt_config(self, data, password):
        """加密配置数据"""
        # 生成随机盐和IV
        salt = os.urandom(16)
        iv = os.urandom(12)
        # 从密码派生密钥
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        key = kdf.derive(password.encode())
        # 使用AES-GCM加密
        cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
        encryptor = cipher.encryptor()
        ciphertext = encryptor.update(data.encode()) + encryptor.finalize()
        # 返回 salt + iv + tag + ciphertext
        return salt + iv + encryptor.tag + ciphertext
    def _decrypt_config(self, encrypted_data, password):
        """解密配置数据"""
        # 分离各部分
        salt = encrypted_data[:16]
        iv = encrypted_data[16:28]
        tag = encrypted_data[28:44]
        ciphertext = encrypted_data[44:]
        # 从密码派生密钥
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        key = kdf.derive(password.encode())
        # 解密
        cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag))
        decryptor = cipher.decryptor()
        return (decryptor.update(ciphertext) + decryptor.finalize()).decode()
    def load_saved_config(self):
        """加载保存的配置(解密)"""
        try:
            if not os.path.exists(self.config_path):
                return False
            # 尝试使用机器特征作为密码解密
            machine_id = self._get_machine_id()
            with open(self.config_path, 'rb') as f:
                encrypted_data = f.read()
            config_json = self._decrypt_config(encrypted_data, machine_id)
            config_data = json.loads(config_json)
            self.master_password_hash = config_data['master_password_hash']
            # 从密钥文件加载盐值和迭代次数
            if not os.path.exists(self.key_path):
                return False
            with open(self.key_path, 'rb') as f:
                self.salt = base64.b64decode(f.readline().strip())
                self.iterations = int(f.readline().strip())
            # 使用主密码哈希派生密钥(与正常登录流程一致)
            kdf = PBKDF2HMAC(
                algorithm=hashes.SHA256(),
                length=32,
                salt=self.salt,
                iterations=self.iterations,
            )
            self.key = kdf.derive(self.master_password_hash.encode())
            # 验证密钥是否与数据库匹配
            if not self.validate_key():
                # 密钥验证失败,删除配置文件强制重新验证
                try:
                    os.remove(self.config_path)
                except:
                    pass
                return False
            return True
        except Exception as e:
            # 如果配置文件损坏,删除它
            try:
                os.remove(self.config_path)
            except:
                pass
            return False
    def _get_machine_id(self):
        """获取机器特征ID用于配置加密"""
        try:
            # 使用多个系统特征生成唯一ID
            import platform
            machine_info = f"{platform.node()}{platform.machine()}{platform.processor()}"
            return hashlib.sha256(machine_info.encode()).hexdigest()[:32]
        except:
            # 如果获取失败,使用固定字符串
            return "default_machine_id_for_config"
    def handle_missing_key_file(self):
        """处理密钥文件丢失的情况"""
        # 显示警告对话框
        warning_msg = """
⚠️ 检测到密钥文件丢失!
数据库文件存在但密钥文件 (secret.key) 丢失。
这可能是由于以下原因:
• 密钥文件被意外删除
• 程序目录被移动
• 文件权限问题
请选择恢复方式:
        """
        # 创建恢复对话框
        recovery_dialog = tk.Toplevel()
        recovery_dialog.title('🔑 密钥文件恢复')
        recovery_dialog.geometry('500x400')
        recovery_dialog.resizable(False, False)
        recovery_dialog.grab_set()
        recovery_dialog.configure(bg='#fff3cd')
        # 居中显示
        recovery_dialog.update_idletasks()
        width = recovery_dialog.winfo_width()
        height = recovery_dialog.winfo_height()
        x = (recovery_dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (recovery_dialog.winfo_screenheight() // 2) - (height // 2)
        recovery_dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        frame = ttk.Frame(recovery_dialog, padding='20')
        frame.pack(fill=tk.BOTH, expand=True)
        # 警告图标和标题
        ttk.Label(frame, text='⚠️', font=('Microsoft YaHei UI', 48), foreground='#856404').pack(pady=(0, 10))
        ttk.Label(frame, text='密钥文件丢失', font=('Microsoft YaHei UI', 16, 'bold'), foreground='#856404').pack(pady=5)
        # 说明文本
        text_widget = tk.Text(frame, wrap=tk.WORD, height=8, width=50, font=('Microsoft YaHei UI', 10))
        text_widget.insert(tk.END, warning_msg)
        text_widget.config(state=tk.DISABLED, bg='#fff3cd', relief=tk.FLAT)
        text_widget.pack(pady=10, fill=tk.BOTH, expand=True)
        # 按钮框架
        btn_frame = ttk.Frame(frame)
        btn_frame.pack(pady=20)
        def try_master_password_recovery():
            """尝试使用主密码恢复"""
            recovery_dialog.destroy()
            self.master_password_recovery()
        def restore_from_backup():
            """从备份恢复"""
            file_path = filedialog.askopenfilename(
                title="选择密钥文件备份",
                filetypes=[("密钥文件", "*.key"), ("所有文件", "*.*")]
            )
            if file_path:
                try:
                    import shutil
                    shutil.copy2(file_path, self.key_path)
                    # 验证恢复的密钥文件是否有效
                    if self.verify_restored_key():
                        messagebox.showinfo("恢复成功", "密钥文件已恢复!程序将重新启动。")
                        recovery_dialog.destroy()
                        self.setup()  # 重新初始化
                    else:
                        messagebox.showerror("恢复失败", "恢复的密钥文件与数据库不匹配!\n请确认选择了正确的密钥文件备份。")
                        # 删除无效的密钥文件
                        try:
                            os.remove(self.key_path)
                        except:
                            pass
                except Exception as e:
                    messagebox.showerror("恢复失败", f"无法恢复密钥文件:{str(e)}")
        def create_new_database():
            """创建新数据库"""
            confirm = messagebox.askyesno("确认操作",
                "这将创建一个全新的数据库,原有数据将无法访问。\n确定要继续吗?")
            if confirm:
                try:
                    # 备份原数据库
                    backup_name = f"passwords_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.db"
                    import shutil
                    shutil.copy2(self.db_path, backup_name)
                    # 删除原数据库和配置
                    os.remove(self.db_path)
                    if os.path.exists(self.config_path):
                        os.remove(self.config_path)
                    messagebox.showinfo("备份完成", f"原数据库已备份为:{backup_name}")
                    recovery_dialog.destroy()
                    self.setup()  # 重新初始化
                except Exception as e:
                    messagebox.showerror("操作失败", f"无法创建新数据库:{str(e)}")
        def close_recovery_dialog():
            """关闭恢复对话框并退出程序"""
            recovery_dialog.destroy()
            # 显示提示信息并退出程序
            messagebox.showwarning("程序退出", "密钥文件丢失,无法访问现有数据。\n程序将退出。")
            exit()
        # 恢复选项按钮
        ttk.Button(btn_frame, text='🔑 使用主密码恢复', command=try_master_password_recovery, width=20).pack(pady=5)
        ttk.Button(btn_frame, text='📁 从备份恢复', command=restore_from_backup, width=20).pack(pady=5)
        ttk.Button(btn_frame, text='🆕 创建新数据库', command=create_new_database, width=20).pack(pady=5)
        ttk.Button(btn_frame, text='❌ 退出程序', command=close_recovery_dialog, width=20).pack(pady=5)
        # 设置对话框关闭事件
        recovery_dialog.protocol("WM_DELETE_WINDOW", close_recovery_dialog)
    def master_password_recovery(self):
        """使用主密码尝试恢复密钥文件"""
        # 从数据库获取主密码哈希
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT value FROM settings WHERE name = ?', ('master_password_hash',))
            result = c.fetchone()
            conn.close()
            if not result:
                messagebox.showerror('恢复失败', '数据库中未找到主密码信息!')
                return
            stored_hash = result[0]
            # 创建密码输入对话框
            password_dialog = tk.Toplevel()
            password_dialog.title('🔑 主密码恢复')
            password_dialog.geometry('400x250')
            password_dialog.resizable(False, False)
            password_dialog.grab_set()
            password_dialog.configure(bg='#f8f9fa')
            # 居中显示
            password_dialog.update_idletasks()
            width = password_dialog.winfo_width()
            height = password_dialog.winfo_height()
            x = (password_dialog.winfo_screenwidth() // 2) - (width // 2)
            y = (password_dialog.winfo_screenheight() // 2) - (height // 2)
            password_dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
            frame = ttk.Frame(password_dialog, padding='30')
            frame.pack(fill=tk.BOTH, expand=True)
            ttk.Label(frame, text='🔑', font=('Microsoft YaHei UI', 32)).pack(pady=(0, 10))
            ttk.Label(frame, text='密钥文件恢复', font=('Microsoft YaHei UI', 14, 'bold')).pack(pady=5)
            ttk.Label(frame, text='请输入主密码以重新生成密钥文件', font=('Microsoft YaHei UI', 10)).pack(pady=5)
            password_var = tk.StringVar()
            password_entry = ttk.Entry(frame, textvariable=password_var, show='*', width=30, font=('Microsoft YaHei UI', 10))
            password_entry.pack(pady=10)
            password_entry.focus()
            def attempt_recovery():
                password = password_var.get()
                if password and hashlib.sha256(password.encode()).hexdigest() == stored_hash:
                    try:
                        # 重新生成密钥文件
                        self.salt = os.urandom(16)
                        self.generate_key()
                        # 使用主密码哈希派生密钥
                        self.load_key_with_hash(stored_hash)
                        # 重新创建密钥验证数据
                        self.create_key_validation()
                        self.save_config(password)
                        messagebox.showinfo('恢复成功', '密钥文件已成功恢复!')
                        password_dialog.destroy()
                    except Exception as e:
                        messagebox.showerror('恢复失败', f'无法恢复密钥文件:{str(e)}')
                else:
                    messagebox.showerror('密码错误', '主密码不正确,请重试!')
                    password_var.set('')
                    password_entry.focus()
            password_entry.bind('', lambda e: attempt_recovery())
            btn_frame = ttk.Frame(frame)
            btn_frame.pack(pady=15)
            ttk.Button(btn_frame, text='🔓 恢复', command=attempt_recovery, width=15).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text='❌ 取消', command=password_dialog.destroy, width=15).pack(side=tk.LEFT, padx=5)
        except Exception as e:
            messagebox.showerror('错误', f'无法访问数据库:{str(e)}')
    def generate_key(self):
        # 生成盐值
        self.salt = os.urandom(16)
        # 确保密钥目录存在
        key_dir = os.path.dirname(self.key_path)
        if not os.path.exists(key_dir):
            os.makedirs(key_dir, exist_ok=True)
        # 保存盐值和迭代次数到密钥文件
        with open(self.key_path, 'wb') as f:
            f.write(base64.b64encode(self.salt) + b'\n')
            f.write(str(self.iterations).encode() + b'\n')
        messagebox.showinfo('信息', '加密参数已初始化')
        # 生成并保存密钥验证数据
        self.create_key_validation()
    def load_key(self, master_password):
        # 从密钥文件加载盐值和迭代次数
        try:
            if not os.path.exists(self.key_path):
                messagebox.showerror('错误', f'密钥文件不存在: {self.key_path}')
                return False
            
            with open(self.key_path, 'rb') as f:
                self.salt = base64.b64decode(f.readline().strip())
                self.iterations = int(f.readline().strip())
            # 使用PBKDF2HMAC从主密码派生密钥
            kdf = PBKDF2HMAC(
                algorithm=hashes.SHA256(),
                length=32,
                salt=self.salt,
                iterations=self.iterations,
            )
            self.key = kdf.derive(master_password.encode())
            return True
        except Exception as e:
            messagebox.showerror('错误', f'加载加密参数失败: {str(e)}')
            return False
    def load_key_with_hash(self, master_password_hash):
        """使用主密码哈希派生密钥"""
        try:
            if not os.path.exists(self.key_path):
                messagebox.showerror('错误', f'密钥文件不存在: {self.key_path}')
                return False
            with open(self.key_path, 'rb') as f:
                self.salt = base64.b64decode(f.readline().strip())
                self.iterations = int(f.readline().strip())
            # 使用PBKDF2HMAC从主密码哈希派生密钥
            kdf = PBKDF2HMAC(
                algorithm=hashes.SHA256(),
                length=32,
                salt=self.salt,
                iterations=self.iterations,
            )
            self.key = kdf.derive(master_password_hash.encode())
            return True
        except Exception as e:
            messagebox.showerror('错误', f'加载加密参数失败: {str(e)}')
            return False
    def create_key_validation(self):
        """创建密钥验证数据"""
        try:
            if not self.key:
                return False
            # 生成密钥哈希
            key_hash = hashlib.sha256(self.key).hexdigest()
            # 创建验证数据(使用已知字符串加密)
            validation_text = "PASSWORD_MANAGER_KEY_VALIDATION_2025"
            validation_encrypted = self.encrypt_password(validation_text)
            if validation_encrypted:
                conn = sqlite3.connect(self.db_path)
                c = conn.cursor()
                # 清除旧的验证数据
                c.execute('DELETE FROM key_validation')
                # 插入新的验证数据
                c.execute('INSERT INTO key_validation (key_hash, validation_data) VALUES (?, ?)',
                          (key_hash, validation_encrypted))
                conn.commit()
                conn.close()
                return True
        except Exception as e:
            print(f"创建密钥验证失败: {str(e)}")
            return False
    def validate_key(self):
        """验证当前密钥是否与数据库匹配"""
        try:
            if not self.key:
                return False
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT key_hash, validation_data FROM key_validation ORDER BY created_at DESC LIMIT 1')
            result = c.fetchone()
            if not result:
                # 如果没有验证数据,尝试验证现有密码
                c.execute('SELECT encrypted_password FROM passwords LIMIT 1')
                password_result = c.fetchone()
                conn.close()
                if password_result:
                    # 尝试解密现有密码来验证密钥
                    try:
                        decrypted = self.decrypt_password(password_result[0])
                        if decrypted:  # 如果能成功解密,说明密钥正确
                            # 创建验证数据
                            return self.create_key_validation()
                        else:
                            return False
                    except:
                        return False
                else:
                    # 没有密码数据,创建验证数据
                    return self.create_key_validation()
            else:
                conn.close()
                stored_key_hash, validation_encrypted = result
                current_key_hash = hashlib.sha256(self.key).hexdigest()
                # 检查密钥哈希是否匹配
                if stored_key_hash != current_key_hash:
                    return False
                # 尝试解密验证数据
                validation_text = self.decrypt_password(validation_encrypted)
                expected_text = "PASSWORD_MANAGER_KEY_VALIDATION_2025"
                return validation_text == expected_text
        except Exception as e:
            print(f"密钥验证失败: {str(e)}")
            return False
    def verify_restored_key(self):
        """验证恢复的密钥文件是否与数据库匹配"""
        try:
            # 获取主密码哈希
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT value FROM settings WHERE name = ?', ('master_password_hash',))
            result = c.fetchone()
            conn.close()
            if not result:
                return False
            master_password_hash = result[0]
            # 尝试使用主密码哈希作为密码来加载密钥
            temp_password = master_password_hash[:32]  # 使用哈希的前32位作为临时密码
            # 保存当前状态
            old_key = self.key
            old_salt = self.salt
            old_iterations = self.iterations
            try:
                # 尝试加载恢复的密钥文件
                with open(self.key_path, 'rb') as f:
                    self.salt = base64.b64decode(f.readline().strip())
                    self.iterations = int(f.readline().strip())
                # 使用多种可能的密码尝试验证
                for attempt_password in [master_password_hash, temp_password]:
                    try:
                        kdf = PBKDF2HMAC(
                            algorithm=hashes.SHA256(),
                            length=32,
                            salt=self.salt,
                            iterations=self.iterations,
                        )
                        self.key = kdf.derive(attempt_password.encode())
                        # 验证密钥
                        if self.validate_key():
                            return True
                    except:
                        continue
                return False
            except Exception:
                return False
            finally:
                # 恢复原状态
                self.key = old_key
                self.salt = old_salt
                self.iterations = old_iterations
        except Exception as e:
            print(f"验证恢复密钥失败: {str(e)}")
            return False
    def create_database(self):
        # 创建SQLite数据库和表
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            # 创建密码表
            c.execute('''CREATE TABLE IF NOT EXISTS passwords
                         (id INTEGER PRIMARY KEY AUTOINCREMENT,
                         service TEXT NOT NULL,
                         username TEXT NOT NULL,
                         encrypted_password TEXT NOT NULL,
                         url TEXT,
                         notes TEXT,
                         category TEXT DEFAULT '默认',
                         expires_at TIMESTAMP,
                         created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                         updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
            # 创建密码历史表
            c.execute('''CREATE TABLE IF NOT EXISTS password_history
                         (id INTEGER PRIMARY KEY AUTOINCREMENT,
                         password_id INTEGER,
                         encrypted_password TEXT NOT NULL,
                         changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                         FOREIGN KEY (password_id) REFERENCES passwords (id))''')
            # 创建分类表
            c.execute('''CREATE TABLE IF NOT EXISTS categories
                         (id INTEGER PRIMARY KEY AUTOINCREMENT,
                         name TEXT UNIQUE NOT NULL,
                         color TEXT DEFAULT '#007bff',
                         created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
            # 创建设置表
            c.execute('''CREATE TABLE IF NOT EXISTS settings
                         (name TEXT PRIMARY KEY,
                         value TEXT NOT NULL)''')
            # 创建密钥验证表
            c.execute('''CREATE TABLE IF NOT EXISTS key_validation
                         (id INTEGER PRIMARY KEY,
                         key_hash TEXT NOT NULL,
                         validation_data TEXT NOT NULL,
                         created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
            # 添加默认分类
            c.execute('''INSERT OR IGNORE INTO categories (name, color) VALUES
                         ('默认', '#6c757d'),
                         ('工作', '#007bff'),
                         ('个人', '#28a745'),
                         ('社交', '#17a2b8'),
                         ('金融', '#ffc107'),
                         ('购物', '#fd7e14')''')
            # 检查并添加新列(用于数据库升级)
            try:
                c.execute('ALTER TABLE passwords ADD COLUMN category TEXT DEFAULT "默认"')
            except sqlite3.OperationalError:
                pass  # 列已存在
            try:
                c.execute('ALTER TABLE passwords ADD COLUMN expires_at TIMESTAMP')
            except sqlite3.OperationalError:
                pass  # 列已存在
            conn.commit()
            conn.close()
        except Exception as e:
            messagebox.showerror('错误', f'创建数据库失败: {str(e)}')
            exit(1)
    def set_master_password(self):
        # 设置主密码
        while True:
            master_password = simpledialog.askstring('设置主密码', '请设置主密码 (至少8位,包含大小写字母和数字):', show='*')
            if not master_password:
                messagebox.showerror('错误', '主密码不能为空!')
                continue
            # 密码强度检查
            if len(master_password)  0:
                messagebox.showerror('错误', f'密码错误! 还剩 {remaining} 次尝试机会')
            else:
                messagebox.showerror('错误', '尝试次数过多,程序将退出')
                exit(1)
    def encrypt_password(self, password):
        # 使用AES-GCM加密
        try:
            # 生成随机IV
            iv = os.urandom(12)
            # 创建加密器
            cipher = Cipher(algorithms.AES(self.key), modes.GCM(iv))
            encryptor = cipher.encryptor()
            # 加密数据
            ciphertext = encryptor.update(password.encode()) + encryptor.finalize()
            # 返回IV + 标签 + 密文 (都进行base64编码以便存储)
            return base64.b64encode(iv + encryptor.tag + ciphertext).decode()
        except Exception as e:
            messagebox.showerror('错误', f'加密失败: {str(e)}')
            return None
    def decrypt_password(self, encrypted_password):
        # 使用AES-GCM解密
        try:
            # 解码base64数据
            data = base64.b64decode(encrypted_password.encode())
            # 分离IV、标签和密文
            iv = data[:12]
            tag = data[12:28]
            ciphertext = data[28:]
            # 创建解密器
            cipher = Cipher(algorithms.AES(self.key), modes.GCM(iv, tag))
            decryptor = cipher.decryptor()
            # 解密数据
            return (decryptor.update(ciphertext) + decryptor.finalize()).decode()
        except Exception as e:
            messagebox.showerror('错误', f'解密失败: {str(e)}')
            return None
    def decrypt_password_legacy(self, encrypted_password):
        """使用原始主密码派生的密钥解密(兼容性方法)"""
        try:
            # 保存当前密钥
            current_key = self.key
            # 尝试使用原始主密码派生密钥
            if hasattr(self, 'master_password_hash'):
                # 从数据库获取主密码哈希
                conn = sqlite3.connect(self.db_path)
                c = conn.cursor()
                c.execute('SELECT value FROM settings WHERE name = ?', ('master_password_hash',))
                result = c.fetchone()
                conn.close()
                if result:
                    master_password_hash = result[0]
                    # 尝试多种可能的密钥派生方式
                    for password_candidate in [master_password_hash, master_password_hash[:32]]:
                        try:
                            # 使用原始密码派生密钥
                            kdf = PBKDF2HMAC(
                                algorithm=hashes.SHA256(),
                                length=32,
                                salt=self.salt,
                                iterations=self.iterations,
                            )
                            legacy_key = kdf.derive(password_candidate.encode())
                            # 临时使用legacy密钥
                            self.key = legacy_key
                            # 尝试解密
                            decrypted = self.decrypt_password(encrypted_password)
                            if decrypted:
                                # 恢复原密钥
                                self.key = current_key
                                return decrypted
                        except:
                            continue
            # 恢复原密钥
            self.key = current_key
            return None
        except Exception as e:
            # 恢复原密钥
            if 'current_key' in locals():
                self.key = current_key
            print(f"兼容性解密失败: {str(e)}")
            return None
    def add_password(self, service, username, password, url='', notes=''):
        # 添加新密码到数据库
        if not all([service, username, password]):
            messagebox.showerror('错误', '服务名、用户名和密码不能为空!')
            return False
        encrypted_password = self.encrypt_password(password)
        if not encrypted_password:
            return False
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            # 检查是否已存在相同服务和用户名的密码
            c.execute('SELECT id FROM passwords WHERE service = ? AND username = ?',
                      (service, username))
            if c.fetchone():
                messagebox.showerror('错误', f'已存在{service}的{username}记录!')
                conn.close()
                return False
            c.execute('INSERT INTO passwords (service, username, encrypted_password, url, notes) VALUES (?, ?, ?, ?, ?)',
                      (service, username, encrypted_password, url, notes))
            conn.commit()
            conn.close()
            messagebox.showinfo('成功', '密码添加成功!')
            return True
        except Exception as e:
            messagebox.showerror('错误', f'添加密码失败: {str(e)}')
            return False
    def get_password(self, service, username):
        # 获取密码
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT encrypted_password FROM passwords WHERE service = ? AND username = ?',
                      (service, username))
            result = c.fetchone()
            conn.close()
            if result:
                # 尝试用当前密钥解密
                decrypted = self.decrypt_password(result[0])
                if decrypted:
                    return decrypted
                # 如果失败,尝试用原始主密码派生的密钥解密(兼容性)
                return self.decrypt_password_legacy(result[0])
            return None
        except Exception as e:
            messagebox.showerror('错误', f'获取密码失败: {str(e)}')
            return None
    def update_password(self, password_id, service, username, new_password, url, notes):
        # 更新密码
        if new_password:  # 只有当提供新密码时才加密
            encrypted_password = self.encrypt_password(new_password)
            if not encrypted_password:
                return False
        else:
            # 如果没有提供新密码,获取当前加密密码
            try:
                conn = sqlite3.connect(self.db_path)
                c = conn.cursor()
                c.execute('SELECT encrypted_password FROM passwords WHERE id = ?', (password_id,))
                result = c.fetchone()
                conn.close()
                if not result:
                    messagebox.showerror('错误', '未找到密码记录!')
                    return False
                encrypted_password = result[0]
            except Exception as e:
                messagebox.showerror('错误', f'获取当前密码失败: {str(e)}')
                return False
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('UPDATE passwords SET service = ?, username = ?, encrypted_password = ?, url = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                      (service, username, encrypted_password, url, notes, password_id))
            conn.commit()
            affected = c.rowcount
            conn.close()
            if affected > 0:
                messagebox.showinfo('成功', '记录更新成功!')
                return True
            else:
                messagebox.showerror('错误', '未找到密码记录!')
                return False
        except Exception as e:
            messagebox.showerror('错误', f'更新记录失败: {str(e)}')
            return False
    def delete_password(self, password_id):
        # 删除密码
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('DELETE FROM passwords WHERE id = ?', (password_id,))
            conn.commit()
            affected = c.rowcount
            conn.close()
            if affected > 0:
                messagebox.showinfo('成功', '密码删除成功!')
                return True
            else:
                messagebox.showerror('错误', '未找到密码记录!')
                return False
        except Exception as e:
            messagebox.showerror('错误', f'删除密码失败: {str(e)}')
            return False
    def get_all_passwords(self):
        # 获取所有密码
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('SELECT id, service, username, url, notes, created_at, updated_at FROM passwords ORDER BY service')
            results = c.fetchall()
            conn.close()
            return results
        except Exception as e:
            messagebox.showerror('错误', f'获取密码列表失败: {str(e)}')
            return []
    def search_passwords(self, keyword):
        # 搜索密码
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            query = "SELECT id, service, username, url, notes, created_at, updated_at FROM passwords WHERE service LIKE ? OR username LIKE ? OR url LIKE ? OR notes LIKE ? ORDER BY service"
            c.execute(query, (f'%{keyword}%', f'%{keyword}%', f'%{keyword}%', f'%{keyword}%'))
            results = c.fetchall()
            conn.close()
            return results
        except Exception as e:
            messagebox.showerror('错误', f'搜索密码失败: {str(e)}')
            return []
    def generate_strong_password(self, length=16):
        # 生成强密码
        if length ?'
        # 确保包含每种类型的字符
        password = [
            random.choice(uppercase),
            random.choice(lowercase),
            random.choice(digits),
            random.choice(special_chars)
        ]
        # 填充剩余长度
        all_chars = uppercase + lowercase + digits + special_chars
        password += [random.choice(all_chars) for _ in range(length - 4)]
        # 打乱顺序
        random.shuffle(password)
        return ''.join(password)
    def export_to_csv(self, file_path):
        """导出密码到CSV文件"""
        try:
            conn = sqlite3.connect(self.db_path)
            c = conn.cursor()
            c.execute('''SELECT service, username, url, notes, category,
                         created_at, updated_at FROM passwords ORDER BY service''')
            results = c.fetchall()
            conn.close()
            with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow(['服务名', '用户名', '密码', 'URL', '备注', '分类', '创建时间', '更新时间'])
                for row in results:
                    # 解密密码
                    password = self.get_password(row[0], row[1])
                    writer.writerow([row[0], row[1], password, row[2], row[3], row[4], row[5], row[6]])
            return True, f"成功导出 {len(results)} 条记录到 {file_path}"
        except Exception as e:
            return False, f"导出失败: {str(e)}"
    def import_from_csv(self, file_path):
        """从CSV文件导入密码"""
        try:
            imported_count = 0
            skipped_count = 0
            with open(file_path, 'r', encoding='utf-8') as csvfile:
                reader = csv.DictReader(csvfile)
                for row in reader:
                    service = row.get('服务名', '').strip()
                    username = row.get('用户名', '').strip()
                    password = row.get('密码', '').strip()
                    url = row.get('URL', '').strip()
                    notes = row.get('备注', '').strip()
                    category = row.get('分类', '默认').strip()
                    if not service or not username or not password:
                        skipped_count += 1
                        continue
                    # 检查是否已存在
                    conn = sqlite3.connect(self.db_path)
                    c = conn.cursor()
                    c.execute('SELECT id FROM passwords WHERE service = ? AND username = ?',
                              (service, username))
                    if c.fetchone():
                        skipped_count += 1
                        conn.close()
                        continue
                    # 加密密码并添加
                    encrypted_password = self.encrypt_password(password)
                    if encrypted_password:
                        c.execute('''INSERT INTO passwords
                                   (service, username, encrypted_password, url, notes, category)
                                   VALUES (?, ?, ?, ?, ?, ?)''',
                                  (service, username, encrypted_password, url, notes, category))
                        conn.commit()
                        imported_count += 1
                    else:
                        skipped_count += 1
                    conn.close()
            return True, f"成功导入 {imported_count} 条记录,跳过 {skipped_count} 条记录"
        except Exception as e:
            return False, f"导入失败: {str(e)}"
class PasswordManagerGUI:
    def __init__(self, root):
        self.root = root
self.root.title('安全密码管理器 v1.0 by:危阿杰  吾爱破解: www.52pojie.cn')        self.root.geometry('1200x700')
        self.root.resizable(True, True)
        self.root.configure(bg='#f8f9fa')
        # 设置现代化样式
        self.style = ttk.Style()
        self.style.theme_use('clam')
        self.style.configure('TLabel', font=('Microsoft YaHei UI', 10), background='#f8f9fa')
        self.style.configure('TButton', font=('Microsoft YaHei UI', 10), padding=6)
        self.style.configure('Treeview', font=('Microsoft YaHei UI', 9), rowheight=25)
        self.style.configure('Treeview.Heading', font=('Microsoft YaHei UI', 10, 'bold'))
        self.style.configure('Title.TLabel', font=('Microsoft YaHei UI', 18, 'bold'), background='#f8f9fa', foreground='#2c3e50')
        # 创建密码管理器实例
        self.password_manager = PasswordManager()
        # 检查密码管理器是否成功初始化
        if not hasattr(self.password_manager, 'key') or not self.password_manager.key:
            # 密码管理器未完全初始化(可能是密钥文件丢失),隐藏主窗口
            self.root.withdraw()
            # 等待用户处理密钥文件问题
            self.wait_for_key_recovery()
            return
        # 创建界面组件
        self.create_widgets()
        # 加载密码列表
        self.update_password_list()
        # 启动自动锁定监控
        self.password_manager.auto_lock_manager.start_monitoring(self.lock_application)
        # 绑定活动事件
        self.bind_activity_events()
        # 绑定快捷键
        self.bind_shortcuts()
    def wait_for_key_recovery(self):
        """等待密钥恢复完成"""
        def check_recovery():
            if hasattr(self.password_manager, 'key') and self.password_manager.key:
                # 密钥恢复成功,显示主窗口并初始化界面
                self.root.deiconify()
                self.create_widgets()
                self.update_password_list()
                self.password_manager.auto_lock_manager.start_monitoring(self.lock_application)
                self.bind_activity_events()
                self.bind_shortcuts()
            else:
                # 继续等待,每500ms检查一次
                self.root.after(500, check_recovery)
        # 开始检查
        check_recovery()
    def create_menu(self):
        """创建菜单栏"""
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        # 获取快捷键管理器
        sm = self.password_manager.shortcut_manager
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label=f"📥 导入CSV\t{sm.get_shortcut('import_csv')}", command=self.import_csv)
        file_menu.add_command(label=f"📤 导出CSV\t{sm.get_shortcut('export_csv')}", command=self.export_csv)
        file_menu.add_separator()
        file_menu.add_command(label="💾 备份密钥文件", command=self.backup_key_file)
        file_menu.add_command(label="🔄 恢复密钥文件", command=self.restore_key_file)
        file_menu.add_separator()
        file_menu.add_command(label=f"🔒 锁定应用\t{sm.get_shortcut('lock_app')}", command=self.lock_application)
        file_menu.add_separator()
        file_menu.add_command(label="❌ 退出", command=self.root.quit)
        # 编辑菜单
        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label=f"➕ 添加密码\t{sm.get_shortcut('add_password')}", command=self.add_password_dialog)
        edit_menu.add_command(label=f"👁️ 查看密码\t{sm.get_shortcut('view_password')}", command=self.view_password)
        edit_menu.add_command(label=f"✏️ 编辑密码\t{sm.get_shortcut('edit_password')}", command=self.update_password_dialog)
        edit_menu.add_command(label=f"🗑️ 删除密码\t{sm.get_shortcut('delete_password')}", command=self.delete_password)
        edit_menu.add_separator()
        edit_menu.add_command(label=f"🎲 生成密码\t{sm.get_shortcut('generate_password')}", command=self.generate_password)
        edit_menu.add_command(label=f"🔍 搜索\t{sm.get_shortcut('search')}", command=lambda: self.search_entry.focus())
        # 工具菜单
        tools_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="工具", menu=tools_menu)
        tools_menu.add_command(label="🔍 密码强度检查", command=self.check_all_passwords_strength)
        tools_menu.add_command(label="⏰ 过期密码检查", command=self.check_expired_passwords)
        tools_menu.add_separator()
        tools_menu.add_command(label="🏷️ 管理分类", command=self.manage_categories)
        tools_menu.add_separator()
        tools_menu.add_command(label="⌨️ 快捷键设置", command=self.show_shortcut_settings)
        # 视图菜单
        view_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="视图", menu=view_menu)
        # 主题子菜单
        theme_menu = tk.Menu(view_menu, tearoff=0)
        view_menu.add_cascade(label="🎨 主题", menu=theme_menu)
        theme_menu.add_command(label="☀️ 浅色主题", command=lambda: self.change_theme('light'))
        theme_menu.add_command(label="🌙 深色主题", command=lambda: self.change_theme('dark'))
        theme_menu.add_command(label="🔵 蓝色主题", command=lambda: self.change_theme('blue'))
        theme_menu.add_separator()
        theme_menu.add_command(label="🌿 绿色主题", command=lambda: self.change_theme('green'))
        theme_menu.add_command(label="💜 紫色主题", command=lambda: self.change_theme('purple'))
        theme_menu.add_command(label="🧡 橙色主题", command=lambda: self.change_theme('orange'))
        theme_menu.add_separator()
        theme_menu.add_command(label="⚫ 高对比度", command=lambda: self.change_theme('high_contrast'))
        theme_menu.add_command(label="🌃 夜间模式", command=lambda: self.change_theme('night'))
        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="📖 使用说明", command=self.show_help)
        help_menu.add_command(label="🔑 密钥恢复指南", command=self.show_key_recovery_guide)
        help_menu.add_separator()
        help_menu.add_command(label="ℹ️ 关于", command=self.show_about)
    def create_widgets(self):
        # 创建菜单栏
        self.create_menu()
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding='15')
        main_frame.pack(fill=tk.BOTH, expand=True)
        # 创建标题
        title_label = ttk.Label(main_frame, text='🔐 安全密码管理器', style='Title.TLabel')
        title_label.pack(pady=(0, 20))
        # 创建按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(0, 15))
        # 添加按钮 - 使用更现代的图标和文字
        ttk.Button(button_frame, text='➕ 添加密码', command=self.add_password_dialog, width=18).pack(side=tk.LEFT, padx=8)
        ttk.Button(button_frame, text='👁️ 查看密码', command=self.view_password, width=18).pack(side=tk.LEFT, padx=8)
        ttk.Button(button_frame, text='✏️ 更新密码', command=self.update_password_dialog, width=18).pack(side=tk.LEFT, padx=8)
        ttk.Button(button_frame, text='🗑️ 删除密码', command=self.delete_password, width=18).pack(side=tk.LEFT, padx=8)
        ttk.Button(button_frame, text='🎲 生成密码', command=self.generate_password, width=18).pack(side=tk.LEFT, padx=8)
        # 创建搜索框
        search_frame = ttk.Frame(main_frame)
        search_frame.pack(fill=tk.X, pady=(0, 15))
        ttk.Label(search_frame, text='🔍 搜索:', font=('Microsoft YaHei UI', 11)).pack(side=tk.LEFT, padx=(0, 10))
        self.search_var = tk.StringVar()
        self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, font=('Microsoft YaHei UI', 10), width=40)
        self.search_entry.pack(side=tk.LEFT, padx=(0, 10))
        self.search_entry.bind('', lambda event: self.search_passwords())
        ttk.Button(search_frame, text='🔍 搜索', command=self.search_passwords, width=12).pack(side=tk.LEFT, padx=5)
        ttk.Button(search_frame, text='🔄 重置', command=self.reset_search, width=12).pack(side=tk.LEFT, padx=5)
        # 创建密码列表
        columns = ('ID', '名称', '用户名', 'URL', '备注', '创建时间', '更新时间')
        self.password_tree = ttk.Treeview(main_frame, columns=columns, show='headings', selectmode='browse')
        # 排序状态
        self.sort_reverse = {}
        for col in columns:
            self.password_tree.heading(col, text=col, command=lambda c=col: self.sort_column(c))
            if col == 'ID':
                self.password_tree.column(col, width=50, anchor=tk.CENTER)
            elif col in ['创建时间', '更新时间']:
                self.password_tree.column(col, width=150, anchor=tk.CENTER)
            elif col == 'URL':
                self.password_tree.column(col, width=200, anchor=tk.W)
                self.password_tree.tag_configure('url', foreground='blue')
            elif col == '备注':
                self.password_tree.column(col, width=150, anchor=tk.W)
            elif col == '名称':
                self.password_tree.column(col, width=150, anchor=tk.CENTER)
            else:
                self.password_tree.column(col, width=120, anchor=tk.CENTER)
            # 初始化排序状态
            self.sort_reverse[col] = False
        # 添加滚动条
        scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.password_tree.yview)
        self.password_tree.configure(yscroll=scrollbar.set)
        self.password_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, pady=5)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        # 创建状态栏框架
        status_frame = ttk.Frame(self.root)
        status_frame.pack(side=tk.BOTTOM, fill=tk.X)
        # 主状态标签
        self.status_var = tk.StringVar()
        self.status_var.set('就绪')
        status_label = ttk.Label(status_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(2, 5), pady=2)
        # 时间标签
        self.time_var = tk.StringVar()
        time_label = ttk.Label(status_frame, textvariable=self.time_var, relief=tk.SUNKEN, anchor=tk.CENTER, width=20)
        time_label.pack(side=tk.RIGHT, padx=(5, 2), pady=2)
        # 更新时间
        self.update_time()
        # 绑定双击事件查看密码或打开链接
        self.password_tree.bind('', self.on_double_click)
        # 绑定右键菜单
        self.password_tree.bind('[B]', self.show_context_menu)
    def on_double_click(self, event):
        # 获取双击的项和列
        if not self.password_tree.selection():
            return
        item = self.password_tree.selection()[0]
        column = self.password_tree.identify_column(event.x)
        # 列索引从1开始,URL是第4列(索引3)
        if column == '#4':  # '#4'对应第4列,即URL列
            url = self.password_tree.item(item, 'values')[3]
            if url:
                webbrowser.open(url)
            else:
                messagebox.showinfo('提示', '该记录没有URL链接')
        else:
            # 双击其他列时查看密码详情
            self.view_password()
    def update_password_list(self, passwords=None):
        # 更新密码列表
        # 清空现有项
        for item in self.password_tree.get_children():
            self.password_tree.delete(item)
        # 获取密码列表
        if passwords is None:
            passwords = self.password_manager.get_all_passwords()
        # 添加到列表
        for password in passwords:
            item_id = self.password_tree.insert('', tk.END, values=password)
            # 如果URL不为空,设置URL标签
            if password[3]:  # password[3]是URL字段
                self.password_tree.item(item_id, tags=('url',))
        self.status_var.set(f'共 {len(passwords)} 条记录')
    def add_password_dialog(self):
        # 添加密码对话框
        dialog = tk.Toplevel(self.root)
        dialog.title('添加新密码')
        dialog.geometry('550x350')
        dialog.resizable(False, False)
        dialog.transient(self.root)
        dialog.grab_set()
        # 居中显示
        dialog.update_idletasks()
        width = dialog.winfo_width()
        height = dialog.winfo_height()
        x = (dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (dialog.winfo_screenheight() // 2) - (height // 2)
        dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        # 创建框架
        frame = ttk.Frame(dialog, padding='25')
        frame.pack(fill=tk.BOTH, expand=True)
        # 配置列权重
        frame.grid_columnconfigure(1, weight=1)
        # 服务名
        ttk.Label(frame, text='名称:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=0, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        service_var = tk.StringVar()
        service_entry = ttk.Entry(frame, textvariable=service_var, width=30, font=('Microsoft YaHei UI', 10))
        service_entry.grid(row=0, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 用户名
        ttk.Label(frame, text='用户名:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=1, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        username_var = tk.StringVar()
        username_entry = ttk.Entry(frame, textvariable=username_var, width=30, font=('Microsoft YaHei UI', 10))
        username_entry.grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 密码行
        ttk.Label(frame, text='密码:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=2, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        # 密码输入框架
        password_input_frame = ttk.Frame(frame)
        password_input_frame.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        password_input_frame.grid_columnconfigure(0, weight=1)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(password_input_frame, textvariable=password_var, font=('Consolas', 10))
        password_entry.grid(row=0, column=0, sticky=tk.W+tk.E, padx=(0, 10))
        # 生成密码按钮
        def generate_new_password():
            new_password = self.password_manager.generate_strong_password()
            password_var.set(new_password)
            update_strength()
        ttk.Button(password_input_frame, text='生成强密码', command=generate_new_password, width=12).grid(row=0, column=1)
        # 密码强度显示
        strength_frame = ttk.Frame(frame)
        strength_frame.grid(row=3, column=1, columnspan=2, sticky=tk.W+tk.E, pady=(5, 10))
        strength_frame.grid_columnconfigure(1, weight=1)
        strength_label = ttk.Label(strength_frame, text='强度: 未检测', font=('Microsoft YaHei UI', 9))
        strength_label.grid(row=0, column=0, sticky=tk.W)
        strength_bar = ttk.Progressbar(strength_frame, length=250, mode='determinate')
        strength_bar.grid(row=0, column=1, sticky=tk.W+tk.E, padx=(10, 0))
        def update_strength(*args):
            password = password_var.get()
            if password:
                score, level, color, feedback = self.password_manager.strength_checker.check_strength(password)
                strength_label.config(text=f'强度: {level}', foreground=color)
                strength_bar['value'] = score
                if feedback:
                    # 显示最重要的反馈
                    main_feedback = feedback[0] if feedback else ""
                    strength_label.config(text=f'强度: {level} - {main_feedback}')
            else:
                strength_label.config(text='强度: 未检测', foreground='gray')
                strength_bar['value'] = 0
        password_var.trace('w', update_strength)
        # URL
        ttk.Label(frame, text='URL:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=4, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        url_var = tk.StringVar()
        url_entry = ttk.Entry(frame, textvariable=url_var, font=('Microsoft YaHei UI', 10))
        url_entry.grid(row=4, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 备注
        ttk.Label(frame, text='备注:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=5, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        notes_var = tk.StringVar()
        notes_entry = ttk.Entry(frame, textvariable=notes_var, font=('Microsoft YaHei UI', 10))
        notes_entry.grid(row=5, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 按钮框架
        btn_frame = ttk.Frame(frame)
        btn_frame.grid(row=6, column=0, columnspan=3, pady=25)
        def save_password():
            service = service_var.get().strip()
            username = username_var.get().strip()
            password = password_var.get().strip()
            url = url_var.get().strip()
            notes = notes_var.get().strip()
            if self.password_manager.add_password(service, username, password, url, notes):
                self.update_password_list()
                dialog.destroy()
        ttk.Button(btn_frame, text='保存', command=save_password).pack(side=tk.LEFT, padx=10)
        ttk.Button(btn_frame, text='取消', command=dialog.destroy).pack(side=tk.LEFT, padx=10)
    def view_password(self):
        # 查看密码
        selected_item = self.password_tree.selection()
        if not selected_item:
            messagebox.showwarning('提示', '请先选择一条记录!')
            return
        item = self.password_tree.item(selected_item[0])
        password_id, service, username = item['values'][0], item['values'][1], item['values'][2]
        # 获取解密后的密码
        password = self.password_manager.get_password(service, username)
        if not password:
            messagebox.showerror('错误', '无法获取密码!')
            return
        # 显示密码对话框
        dialog = tk.Toplevel(self.root)
        dialog.title(f'👁️ {service} - 密码详情')
        dialog.geometry('650x400')
        dialog.resizable(True, True)
        dialog.transient(self.root)
        dialog.grab_set()
        dialog.configure(bg='#f8f9fa')
        # 居中显示
        dialog.update_idletasks()
        width = dialog.winfo_width()
        height = dialog.winfo_height()
        x = (dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (dialog.winfo_screenheight() // 2) - (height // 2)
        dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        # 创建框架
        frame = ttk.Frame(dialog, padding='25')
        frame.pack(fill=tk.BOTH, expand=True)
        # 标题
        title_label = ttk.Label(frame, text=f'🔐 {service}', font=('Microsoft YaHei UI', 14, 'bold'), foreground='#2c3e50')
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        # 服务名
        ttk.Label(frame, text='名称:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=1, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        service_label = ttk.Label(frame, text=service, font=('Microsoft YaHei UI', 12), foreground='#34495e')
        service_label.grid(row=1, column=1, sticky=tk.W, pady=10, columnspan=2)
        # 用户名
        ttk.Label(frame, text='用户名:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=2, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        username_label = ttk.Label(frame, text=username, font=('Microsoft YaHei UI', 12), foreground='#34495e')
        username_label.grid(row=2, column=1, sticky=tk.W, pady=10, columnspan=2)
        # 密码
        ttk.Label(frame, text='密码:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=3, column=0, sticky=tk.W, pady=12, padx=(0, 15))
        # 密码显示框架
        password_frame = ttk.Frame(frame)
        password_frame.grid(row=3, column=1, sticky=tk.W+tk.E, pady=12, columnspan=2)
        password_frame.grid_columnconfigure(0, weight=1)
        # 密码显示标签 - 直接显示密码文本
        password_display = ttk.Label(password_frame, text=password,
                                   font=('Consolas', 12, 'bold'),
                                   foreground='#e74c3c',
                                   background='#f8f9fa',
                                   relief='solid',
                                   borderwidth=1,
                                   padding=(10, 5))
        password_display.pack(side=tk.LEFT, padx=(0, 15), fill=tk.X, expand=True)
        # 复制按钮
        def copy_to_clipboard():
            pyperclip.copy(password)
            messagebox.showinfo('✅ 成功', '密码已复制到剪贴板!')
        ttk.Button(password_frame, text='📋 复制', command=copy_to_clipboard, width=12).pack(side=tk.RIGHT)
        current_row = 4
        # URL
        if item['values'][3]:
            ttk.Label(frame, text='URL:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=current_row, column=0, sticky=tk.W, pady=8, padx=(0, 15))
            url_text = item['values'][3]
            url_label = ttk.Label(frame, text=url_text, font=('Microsoft YaHei UI', 10), foreground='#3498db', cursor='hand2')
            url_label.grid(row=current_row, column=1, sticky=tk.W, pady=8, columnspan=2)
            # 点击URL打开链接
            def open_url(event):
                webbrowser.open(url_text)
            url_label.bind('[B]', open_url)
            current_row += 1
        # 备注
        if item['values'][4]:
            ttk.Label(frame, text='备注:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=current_row, column=0, sticky=tk.W, pady=8, padx=(0, 15))
            notes_label = ttk.Label(frame, text=item['values'][4], font=('Microsoft YaHei UI', 10), foreground='#34495e', wraplength=300)
            notes_label.grid(row=current_row, column=1, sticky=tk.W, pady=8, columnspan=2)
            current_row += 1
        # 时间信息
        ttk.Label(frame, text='创建时间:', font=('Microsoft YaHei UI', 9)).grid(row=current_row, column=0, sticky=tk.W, pady=(15, 5), padx=(0, 15))
        ttk.Label(frame, text=item['values'][5], font=('Microsoft YaHei UI', 9), foreground='#7f8c8d').grid(row=current_row, column=1, sticky=tk.W, pady=(15, 5))
        ttk.Label(frame, text='更新时间:', font=('Microsoft YaHei UI', 9)).grid(row=current_row+1, column=0, sticky=tk.W, pady=5, padx=(0, 15))
        ttk.Label(frame, text=item['values'][6], font=('Microsoft YaHei UI', 9), foreground='#7f8c8d').grid(row=current_row+1, column=1, sticky=tk.W, pady=5)
        # 按钮框架
        btn_frame = ttk.Frame(frame)
        btn_frame.grid(row=current_row+2, column=0, columnspan=3, pady=(20, 0))
        # 关闭按钮
        ttk.Button(btn_frame, text='❌ 关闭', command=dialog.destroy, width=15).pack(side=tk.RIGHT, padx=10)
        # 编辑按钮
        def edit_password():
            dialog.destroy()
            self.update_password_dialog()
        ttk.Button(btn_frame, text='✏️ 编辑', command=edit_password, width=15).pack(side=tk.RIGHT, padx=10)
    def update_password_dialog(self):
        # 更新密码对话框
        selected_item = self.password_tree.selection()
        if not selected_item:
            messagebox.showwarning('提示', '请先选择一条记录!')
            return
        item = self.password_tree.item(selected_item[0])
        password_id, service, username = item['values'][0], item['values'][1], item['values'][2]
        # 显示更新对话框
        dialog = tk.Toplevel(self.root)
        dialog.title(f'{service} - 更新密码')
        dialog.geometry('550x320')
        dialog.resizable(False, False)
        dialog.transient(self.root)
        dialog.grab_set()
        # 居中显示
        dialog.update_idletasks()
        width = dialog.winfo_width()
        height = dialog.winfo_height()
        x = (dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (dialog.winfo_screenheight() // 2) - (height // 2)
        dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        # 创建框架
        frame = ttk.Frame(dialog, padding='25')
        frame.pack(fill=tk.BOTH, expand=True)
        # 配置列权重
        frame.grid_columnconfigure(1, weight=1)
        # 服务名称
        ttk.Label(frame, text='名称:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=0, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        service_var = tk.StringVar(value=service)
        service_entry = ttk.Entry(frame, textvariable=service_var, font=('Microsoft YaHei UI', 10))
        service_entry.grid(row=0, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 用户名
        ttk.Label(frame, text='用户名:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=1, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        username_var = tk.StringVar(value=username)
        username_entry = ttk.Entry(frame, textvariable=username_var, font=('Microsoft YaHei UI', 10))
        username_entry.grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 密码行
        ttk.Label(frame, text='新密码:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=2, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        # 密码输入框架
        password_input_frame = ttk.Frame(frame)
        password_input_frame.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        password_input_frame.grid_columnconfigure(0, weight=1)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(password_input_frame, textvariable=password_var, font=('Consolas', 10))
        password_entry.grid(row=0, column=0, sticky=tk.W+tk.E, padx=(0, 10))
        # 生成密码按钮
        def generate_new_password():
            new_password = self.password_manager.generate_strong_password()
            password_var.set(new_password)
        ttk.Button(password_input_frame, text='生成强密码', command=generate_new_password, width=12).grid(row=0, column=1)
        # URL
        ttk.Label(frame, text='URL:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=3, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        url_var = tk.StringVar(value=item['values'][3])
        url_entry = ttk.Entry(frame, textvariable=url_var, font=('Microsoft YaHei UI', 10))
        url_entry.grid(row=3, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 备注
        ttk.Label(frame, text='备注:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=4, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        notes_var = tk.StringVar(value=item['values'][4])
        notes_entry = ttk.Entry(frame, textvariable=notes_var, font=('Microsoft YaHei UI', 10))
        notes_entry.grid(row=4, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        # 按钮框架
        btn_frame = ttk.Frame(frame)
        btn_frame.grid(row=5, column=0, columnspan=3, pady=25)
        def save_update():
            service = service_var.get().strip()
            username = username_var.get().strip()
            new_password = password_var.get().strip()
            url = url_var.get().strip()
            notes = notes_var.get().strip()
            # 检查必填字段
            if not service or not username:
                messagebox.showerror('错误', '服务名和用户名不能为空!')
                return
            # 如果没有输入新密码,提示用户
            if not new_password:
                confirm = messagebox.askyesno('确认', '您没有输入新密码,是否只更新其他信息?')
                if not confirm:
                    return
            success = self.password_manager.update_password(password_id, service, username, new_password, url, notes)
            if success:
                self.update_password_list()
                dialog.destroy()
            # update_password 方法内部已经显示了成功或错误消息
        ttk.Button(btn_frame, text='保存', command=save_update).pack(side=tk.LEFT, padx=10)
        ttk.Button(btn_frame, text='取消', command=dialog.destroy).pack(side=tk.LEFT, padx=10)
    def delete_password(self):
        # 删除密码
        selected_item = self.password_tree.selection()
        if not selected_item:
            messagebox.showwarning('提示', '请先选择一条记录!')
            return
        item = self.password_tree.item(selected_item[0])
        password_id, service, username = item['values'][0], item['values'][1], item['values'][2]
        confirm = messagebox.askyesno('确认', f'确定要删除 {service} 的 {username} 密码记录吗?')
        if confirm:
            if self.password_manager.delete_password(password_id):
                self.update_password_list()
    def search_passwords(self):
        # 搜索密码
        keyword = self.search_var.get().strip()
        if not keyword:
            self.update_password_list()
            return
        results = self.password_manager.search_passwords(keyword)
        self.update_password_list(results)
        self.status_var.set(f'搜索到 {len(results)} 条记录')
    def reset_search(self):
        # 重置搜索
        self.search_var.set('')
        self.update_password_list()
    def generate_password(self):
        # 生成密码对话框
        password = self.password_manager.generate_strong_password()
        dialog = tk.Toplevel(self.root)
        dialog.title('🎲 生成强密码')
        dialog.geometry('500x200')
        dialog.resizable(False, False)
        dialog.transient(self.root)
        dialog.grab_set()
        dialog.configure(bg='#f8f9fa')
        # 居中显示
        dialog.update_idletasks()
        width = dialog.winfo_width()
        height = dialog.winfo_height()
        x = (dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (dialog.winfo_screenheight() // 2) - (height // 2)
        dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        # 创建框架
        frame = ttk.Frame(dialog, padding='25')
        frame.pack(fill=tk.BOTH, expand=True)
        # 标题
        title_label = ttk.Label(frame, text='🔐 强密码生成器', font=('Microsoft YaHei UI', 14, 'bold'), foreground='#2c3e50')
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        # 密码显示
        ttk.Label(frame, text='生成的密码:', font=('Microsoft YaHei UI', 10, 'bold')).grid(row=1, column=0, sticky=tk.W, pady=10, padx=(0, 15))
        password_frame = ttk.Frame(frame)
        password_frame.grid(row=1, column=1, sticky=tk.W, pady=10)
        # 密码文本框
        password_var = tk.StringVar(value=password)
        password_entry = ttk.Entry(password_frame, textvariable=password_var, width=25,
                                 font=('Consolas', 12, 'bold'), state='readonly')
        password_entry.pack(side=tk.LEFT, padx=(0, 10))
        # 复制按钮
        def copy_to_clipboard():
            current_password = password_var.get()
            pyperclip.copy(current_password)
            messagebox.showinfo('✅ 成功', '密码已复制到剪贴板!')
            dialog.destroy()
        # 重新生成按钮
        def regenerate_password():
            new_password = self.password_manager.generate_strong_password()
            password_var.set(new_password)
        # 按钮框架
        btn_frame = ttk.Frame(frame)
        btn_frame.grid(row=2, column=0, columnspan=3, pady=(20, 0))
        ttk.Button(btn_frame, text='🔄 重新生成', command=regenerate_password, width=15).pack(side=tk.LEFT, padx=10)
        ttk.Button(btn_frame, text='📋 复制并关闭', command=copy_to_clipboard, width=15).pack(side=tk.LEFT, padx=10)
        ttk.Button(btn_frame, text='❌ 关闭', command=dialog.destroy, width=15).pack(side=tk.LEFT, padx=10)
    def bind_activity_events(self):
        """绑定用户活动事件"""
        def on_activity(event=None):
            self.password_manager.auto_lock_manager.update_activity()
        # 绑定各种用户活动事件
        self.root.bind('', on_activity)
        self.root.bind('[B]', on_activity)
        self.root.bind('', on_activity)
        self.root.bind('', on_activity)
        # 绑定到所有子组件
        def bind_to_children(widget):
            widget.bind('', on_activity, add='+')
            widget.bind('[B]', on_activity, add='+')
            widget.bind('', on_activity, add='+')
            for child in widget.winfo_children():
                bind_to_children(child)
        bind_to_children(self.root)
    def bind_shortcuts(self):
        """绑定快捷键"""
        # 获取快捷键管理器
        sm = self.password_manager.shortcut_manager
        # 绑定动态快捷键
        shortcuts = {
            'add_password': lambda e: self.add_password_dialog(),
            'view_password': lambda e: self.view_password() if self.password_tree.selection() else None,
            'edit_password': lambda e: self.update_password_dialog(),
            'delete_password': lambda e: self.delete_password(),
            'generate_password': lambda e: self.generate_password(),
            'search': lambda e: self.search_entry.focus(),
            'lock_app': lambda e: self.lock_application(),
            'import_csv': lambda e: self.import_csv(),
            'export_csv': lambda e: self.export_csv(),
            'reset_search': lambda e: self.reset_search(),
            'refresh': lambda e: self.update_password_list(),
            'copy_password': self.copy_selected_password
        }
        for action, callback in shortcuts.items():
            shortcut = sm.get_shortcut(action)
            if shortcut:
                binding = sm.format_shortcut_binding(shortcut)
                if binding:
                    try:
                        self.root.bind(binding, callback)
                    except tk.TclError:
                        print(f"无法绑定快捷键: {shortcut} -> {binding}")
        # 固定快捷键(不可自定义)
        self.root.bind('', lambda e: self.password_tree.selection_remove(self.password_tree.selection()))
        self.root.bind('', lambda e: self.view_password() if self.password_tree.selection() else None)
    def copy_selected_password(self, event=None):
        """复制选中项的密码"""
        selected_item = self.password_tree.selection()
        if selected_item:
            item = self.password_tree.item(selected_item[0])
            service, username = item['values'][1], item['values'][2]
            password = self.password_manager.get_password(service, username)
            if password:
                pyperclip.copy(password)
                self.status_var.set(f'已复制 {service} 的密码到剪贴板')
                # 3秒后恢复状态栏
                self.root.after(3000, lambda: self.status_var.set('就绪'))
            else:
                messagebox.showerror('错误', '无法获取密码!')
    def show_context_menu(self, event):
        """显示右键菜单"""
        # 选中右键点击的项
        item = self.password_tree.identify_row(event.y)
        if item:
            self.password_tree.selection_set(item)
            # 创建右键菜单
            context_menu = tk.Menu(self.root, tearoff=0)
            context_menu.add_command(label="👁️ 查看密码", command=self.view_password)
            context_menu.add_command(label="✏️ 编辑密码", command=self.update_password_dialog)
            context_menu.add_command(label="📋 复制密码", command=self.copy_selected_password)
            context_menu.add_separator()
            # 如果有URL,添加打开链接选项
            selected_item = self.password_tree.selection()
            if selected_item:
                item_values = self.password_tree.item(selected_item[0])['values']
                if len(item_values) > 3 and item_values[3]:  # 有URL
                    context_menu.add_command(label="🌐 打开链接",
                                           command=lambda: webbrowser.open(item_values[3]))
                    context_menu.add_separator()
            context_menu.add_command(label="🗑️ 删除密码", command=self.delete_password)
            # 显示菜单
            try:
                context_menu.tk_popup(event.x_root, event.y_root)
            finally:
                context_menu.grab_release()
        else:
            # 空白区域右键菜单
            context_menu = tk.Menu(self.root, tearoff=0)
            context_menu.add_command(label="➕ 添加密码", command=self.add_password_dialog)
            context_menu.add_command(label="🎲 生成密码", command=self.generate_password)
            context_menu.add_separator()
            context_menu.add_command(label="🔄 刷新列表", command=self.update_password_list)
            try:
                context_menu.tk_popup(event.x_root, event.y_root)
            finally:
                context_menu.grab_release()
    def lock_application(self):
        """锁定应用程序"""
        if self.password_manager.is_locked:
            return
        self.password_manager.is_locked = True
        # 创建锁定对话框
        lock_dialog = tk.Toplevel(self.root)
        lock_dialog.title('🔒 应用程序已锁定')
        lock_dialog.geometry('400x200')
        lock_dialog.resizable(False, False)
        lock_dialog.transient(self.root)
        lock_dialog.grab_set()
        lock_dialog.configure(bg='#f8f9fa')
        # 禁用主窗口
        self.root.withdraw()
        # 居中显示
        lock_dialog.update_idletasks()
        width = lock_dialog.winfo_width()
        height = lock_dialog.winfo_height()
        x = (lock_dialog.winfo_screenwidth() // 2) - (width // 2)
        y = (lock_dialog.winfo_screenheight() // 2) - (height // 2)
        lock_dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        frame = ttk.Frame(lock_dialog, padding='30')
        frame.pack(fill=tk.BOTH, expand=True)
        # 锁定图标和提示
        ttk.Label(frame, text='🔒', font=('Microsoft YaHei UI', 48)).pack(pady=(0, 10))
        ttk.Label(frame, text='应用程序已自动锁定', font=('Microsoft YaHei UI', 14, 'bold')).pack(pady=5)
        ttk.Label(frame, text='请输入主密码解锁', font=('Microsoft YaHei UI', 10)).pack(pady=5)
        # 密码输入
        password_var = tk.StringVar()
        password_entry = ttk.Entry(frame, textvariable=password_var, show='*', width=30, font=('Microsoft YaHei UI', 10))
        password_entry.pack(pady=10)
        password_entry.focus()
        def unlock():
            password = password_var.get()
            if password and hashlib.sha256(password.encode()).hexdigest() == self.password_manager.master_password_hash:
                self.password_manager.is_locked = False
                self.password_manager.auto_lock_manager.update_activity()
                lock_dialog.destroy()
                self.root.deiconify()
            else:
                messagebox.showerror('错误', '密码错误!')
                password_var.set('')
                password_entry.focus()
        password_entry.bind('', lambda e: unlock())
        btn_frame = ttk.Frame(frame)
        btn_frame.pack(pady=10)
        ttk.Button(btn_frame, text='🔓 解锁', command=unlock, width=15).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text='❌ 退出', command=self.root.quit, width=15).pack(side=tk.LEFT, padx=5)
    def export_csv(self):
        """导出CSV对话框"""
        file_path = filedialog.asksaveasfilename(
            title="导出密码数据",
            defaultextension=".csv",
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        if file_path:
            success, message = self.password_manager.export_to_csv(file_path)
            if success:
                messagebox.showinfo("导出成功", message)
            else:
                messagebox.showerror("导出失败", message)
    def import_csv(self):
        """导入CSV对话框"""
        file_path = filedialog.askopenfilename(
            title="导入密码数据",
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        if file_path:
            confirm = messagebox.askyesno("确认导入",
                "导入操作将添加新的密码记录。\n已存在的记录将被跳过。\n是否继续?")
            if confirm:
                success, message = self.password_manager.import_from_csv(file_path)
                if success:
                    messagebox.showinfo("导入完成", message)
                    self.update_password_list()
                else:
                    messagebox.showerror("导入失败", message)
    def check_all_passwords_strength(self):
        """检查所有密码强度"""
        passwords = self.password_manager.get_all_passwords()
        weak_passwords = []
        for password_info in passwords:
            service, username = password_info[1], password_info[2]
            password = self.password_manager.get_password(service, username)
            if password:
                score, level, color, feedback = self.password_manager.strength_checker.check_strength(password)
                if score ",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        # 快捷键设置项
        shortcut_entries = {}
        action_names = {
            'add_password': '添加密码',
            'view_password': '查看密码',
            'edit_password': '编辑密码',
            'delete_password': '删除密码',
            'generate_password': '生成密码',
            'search': '搜索',
            'lock_app': '锁定应用',
            'import_csv': '导入CSV',
            'export_csv': '导出CSV',
            'reset_search': '重置搜索',
            'refresh': '刷新',
            'copy_password': '复制密码'
        }
        row = 0
        for action, name in action_names.items():
            # 功能名称
            name_label = ttk.Label(scrollable_frame, text=name,
                                  font=('Microsoft YaHei UI', 10, 'bold'))
            name_label.grid(row=row, column=0, sticky=tk.W, padx=(0, 20), pady=8)
            # 快捷键输入框
            shortcut_var = tk.StringVar(value=self.password_manager.shortcut_manager.get_shortcut(action))
            shortcut_entry = ttk.Entry(scrollable_frame, textvariable=shortcut_var,
                                     width=15, font=('Microsoft YaHei UI', 10))
            shortcut_entry.grid(row=row, column=1, padx=(0, 10), pady=8)
            # 绑定键盘事件
            shortcut_entry.bind('', lambda e, action=action, var=shortcut_var:
                               self.capture_shortcut(e, action, var, dialog))
            shortcut_entry.bind('', lambda e: e.widget.select_range(0, tk.END))
            shortcut_entries[action] = shortcut_var
            # 清除按钮
            clear_btn = ttk.Button(scrollable_frame, text='清除', width=8,
                                  command=lambda a=action, v=shortcut_var: v.set(''))
            clear_btn.grid(row=row, column=2, padx=5, pady=8)
            row += 1
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        # 按钮框架
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=(20, 0))
        def save_shortcuts():
            """保存快捷键设置"""
            # 检查冲突
            conflicts = []
            for action1, var1 in shortcut_entries.items():
                shortcut1 = var1.get().strip()
                if shortcut1:
                    for action2, var2 in shortcut_entries.items():
                        if action1 != action2 and var2.get().strip() == shortcut1:
                            conflicts.append(f"{action_names[action1]} 和 {action_names[action2]} 使用了相同的快捷键: {shortcut1}")
            if conflicts:
                messagebox.showerror('快捷键冲突', '\n'.join(conflicts))
                return
            # 保存设置
            for action, var in shortcut_entries.items():
                self.password_manager.shortcut_manager.set_shortcut(action, var.get().strip())
            if self.password_manager.shortcut_manager.save_shortcuts():
                messagebox.showinfo('成功', '快捷键设置已保存!')
                self.update_shortcuts()  # 更新快捷键绑定
                dialog.destroy()
            else:
                messagebox.showerror('错误', '保存快捷键设置失败!')
        def reset_defaults():
            """重置为默认快捷键"""
            if messagebox.askyesno('确认', '确定要重置为默认快捷键吗?'):
                for action, var in shortcut_entries.items():
                    default_shortcut = self.password_manager.shortcut_manager.default_shortcuts.get(action, '')
                    var.set(default_shortcut)
        # 按钮
        ttk.Button(btn_frame, text='💾 保存', command=save_shortcuts, width=15).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text='🔄 重置默认', command=reset_defaults, width=15).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text='❌ 取消', command=dialog.destroy, width=15).pack(side=tk.RIGHT, padx=5)
    def capture_shortcut(self, event, action, var, dialog):
        """捕获快捷键输入"""
        # 阻止默认行为
        event.widget.focus_set()
        # 构建快捷键字符串
        modifiers = []
        if event.state & 0x4:  # Control
            modifiers.append('Ctrl')
        if event.state & 0x8:  # Alt
            modifiers.append('Alt')
        if event.state & 0x1:  # Shift
            modifiers.append('Shift')
        key = event.keysym
        # 特殊键处理
        if key in ['Control_L', 'Control_R', 'Alt_L', 'Alt_R', 'Shift_L', 'Shift_R']:
            return "break"
        # 功能键
        if key.startswith('F') and key[1:].isdigit():
            shortcut = key
        elif len(key) == 1 and key.isalnum():
            if modifiers:
                shortcut = '+'.join(modifiers) + '+' + key.upper()
            else:
                shortcut = key.upper()
        else:
            return "break"
        var.set(shortcut)
        return "break"
    def update_shortcuts(self):
        """更新快捷键绑定"""
        # 清除所有现有绑定
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        self.root.unbind_all('')
        # 重新绑定快捷键
        self.bind_shortcuts()
    def update_time(self):
        """更新状态栏时间"""
        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.time_var.set(current_time)
        # 每秒更新一次
        self.root.after(1000, self.update_time)
if __name__ == '__main__':
    root = tk.Tk()
    app = PasswordManagerGUI(root)
    app.load_theme()  # 加载保存的主题
    root.mainloop()

密码, 密钥

您需要登录后才可以回帖 登录 | 立即注册

返回顶部