使用VSCode开发少儿编程项目:密码管理器(本地版)

部署运行你感兴趣的模型镜像

使用VSCode开发少儿编程项目:密码管理器(本地版)

我将为您创建一个安全的本地密码管理器,使用Python和加密技术来保护您的各种账号密码。这个项目适合在VSCode中开发。

项目概述

这个密码管理器将包含以下核心功能:

  • 主密码保护
  • AES加密算法保护数据
  • 密码的增删改查
  • 密码强度检查
  • 密码生成器
  • 数据备份和恢复
  • 简单的图形界面

项目结构

password-manager/
│
├── main.py
├── crypto.py
├── password_manager.py
├── gui.py
├── requirements.txt
└── README.md

代码实现

1. requirements.txt

cryptography>=3.4.8
pyperclip>=1.8.2
tkinter

2. crypto.py - 加密模块

import os
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import secrets

class CryptoManager:
    def __init__(self):
        self.backend = default_backend()
    
    def derive_key(self, password: str, salt: bytes) -> bytes:
        """从密码派生加密密钥"""
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
            backend=self.backend
        )
        return kdf.derive(password.encode('utf-8'))
    
    def generate_salt(self) -> bytes:
        """生成随机盐值"""
        return os.urandom(16)
    
    def encrypt(self, plaintext: str, password: str) -> dict:
        """加密数据"""
        try:
            # 生成盐和IV
            salt = self.generate_salt()
            iv = os.urandom(16)
            
            # 派生密钥
            key = self.derive_key(password, salt)
            
            # 创建加密器
            cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=self.backend)
            encryptor = cipher.encryptor()
            
            # 加密数据
            plaintext_bytes = plaintext.encode('utf-8')
            ciphertext = encryptor.update(plaintext_bytes) + encryptor.finalize()
            
            # 返回加密结果
            return {
                'ciphertext': base64.b64encode(ciphertext).decode('utf-8'),
                'salt': base64.b64encode(salt).decode('utf-8'),
                'iv': base64.b64encode(iv).decode('utf-8')
            }
        except Exception as e:
            raise Exception(f"加密失败: {str(e)}")
    
    def decrypt(self, encrypted_data: dict, password: str) -> str:
        """解密数据"""
        try:
            # 解码base64数据
            ciphertext = base64.b64decode(encrypted_data['ciphertext'])
            salt = base64.b64decode(encrypted_data['salt'])
            iv = base64.b64decode(encrypted_data['iv'])
            
            # 派生密钥
            key = self.derive_key(password, salt)
            
            # 创建解密器
            cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=self.backend)
            decryptor = cipher.decryptor()
            
            # 解密数据
            plaintext_bytes = decryptor.update(ciphertext) + decryptor.finalize()
            
            return plaintext_bytes.decode('utf-8')
        except Exception as e:
            raise Exception(f"解密失败: {str(e)}")
    
    def verify_password(self, password: str) -> bool:
        """验证密码强度"""
        if len(password) < 8:
            return False
        
        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)
        has_special = any(not c.isalnum() for c in password)
        
        # 至少满足其中三项
        conditions_met = sum([has_upper, has_lower, has_digit, has_special])
        return conditions_met >= 3

class PasswordGenerator:
    """密码生成器"""
    
    def __init__(self):
        self.lowercase = 'abcdefghijklmnopqrstuvwxyz'
        self.uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        self.digits = '0123456789'
        self.special = '!@#$%^&*()_+-=[]{}|;:,.<>?'
    
    def generate_password(self, length=16, use_upper=True, use_lower=True, 
                         use_digits=True, use_special=True) -> str:
        """生成随机密码"""
        if length < 8:
            raise ValueError("密码长度至少为8位")
        
        # 构建字符集
        charset = ""
        if use_lower:
            charset += self.lowercase
        if use_upper:
            charset += self.uppercase
        if use_digits:
            charset += self.digits
        if use_special:
            charset += self.special
        
        if not charset:
            raise ValueError("至少选择一种字符类型")
        
        # 确保密码包含所有选定的字符类型
        password = []
        if use_lower:
            password.append(secrets.choice(self.lowercase))
        if use_upper:
            password.append(secrets.choice(self.uppercase))
        if use_digits:
            password.append(secrets.choice(self.digits))
        if use_special:
            password.append(secrets.choice(self.special))
        
        # 填充剩余长度
        remaining_length = length - len(password)
        for _ in range(remaining_length):
            password.append(secrets.choice(charset))
        
        # 随机打乱顺序
        secrets.SystemRandom().shuffle(password)
        
        return ''.join(password)
    
    def check_strength(self, password: str) -> dict:
        """检查密码强度"""
        score = 0
        feedback = []
        
        # 长度检查
        if len(password) >= 12:
            score += 2
        elif len(password) >= 8:
            score += 1
        else:
            feedback.append("密码太短(至少8位)")
        
        # 字符类型检查
        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)
        has_special = any(not c.isalnum() for c in password)
        
        char_types = sum([has_upper, has_lower, has_digit, has_special])
        
        if char_types >= 4:
            score += 2
        elif char_types >= 3:
            score += 1
        else:
            feedback.append("需要更多字符类型(大小写字母、数字、特殊字符)")
        
        # 确定强度等级
        if score >= 3:
            strength = "强"
            color = "green"
        elif score >= 2:
            strength = "中"
            color = "orange"
        else:
            strength = "弱"
            color = "red"
        
        return {
            'strength': strength,
            'score': score,
            'color': color,
            'feedback': feedback
        }

# 测试加密功能
if __name__ == "__main__":
    crypto = CryptoManager()
    password = "MyStrongPassword123!"
    data = "这是我的秘密数据"
    
    # 测试加密
    encrypted = crypto.encrypt(data, password)
    print("加密结果:", encrypted)
    
    # 测试解密
    decrypted = crypto.decrypt(encrypted, password)
    print("解密结果:", decrypted)
    
    # 测试密码生成
    generator = PasswordGenerator()
    new_password = generator.generate_password(16)
    print("生成的密码:", new_password)
    print("密码强度:", generator.check_strength(new_password))

3. password_manager.py - 密码管理核心

import json
import os
import getpass
from datetime import datetime
from crypto import CryptoManager, PasswordGenerator

class PasswordManager:
    def __init__(self, data_file="passwords.dat", config_file="config.json"):
        self.data_file = data_file
        self.config_file = config_file
        self.crypto = CryptoManager()
        self.generator = PasswordGenerator()
        self.is_authenticated = False
        self.master_password = None
        
        # 加载配置
        self.config = self._load_config()
    
    def _load_config(self) -> dict:
        """加载配置文件"""
        default_config = {
            "version": "1.0",
            "created_at": datetime.now().isoformat(),
            "backup_enabled": True,
            "auto_lock": 300  # 5分钟后自动锁定
        }
        
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            else:
                self._save_config(default_config)
                return default_config
        except Exception:
            return default_config
    
    def _save_config(self, config: dict):
        """保存配置文件"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"保存配置失败: {e}")
    
    def initialize(self, master_password: str) -> bool:
        """初始化密码管理器"""
        if not self.crypto.verify_password(master_password):
            raise ValueError("主密码强度不足,请使用至少8位包含大小写字母、数字和特殊字符的组合")
        
        # 创建空的密码数据库
        empty_data = {
            "version": "1.0",
            "created_at": datetime.now().isoformat(),
            "last_modified": datetime.now().isoformat(),
            "entries": []
        }
        
        try:
            encrypted_data = self.crypto.encrypt(json.dumps(empty_data), master_password)
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(encrypted_data, f, indent=2)
            
            self.master_password = master_password
            self.is_authenticated = True
            print("密码管理器初始化成功!")
            return True
        except Exception as e:
            raise Exception(f"初始化失败: {e}")
    
    def authenticate(self, master_password: str) -> bool:
        """验证主密码"""
        try:
            if not os.path.exists(self.data_file):
                return self.initialize(master_password)
            
            # 尝试解密数据来验证密码
            with open(self.data_file, 'r', encoding='utf-8') as f:
                encrypted_data = json.load(f)
            
            # 测试解密
            self.crypto.decrypt(encrypted_data, master_password)
            
            self.master_password = master_password
            self.is_authenticated = True
            return True
        except Exception:
            return False
    
    def _load_data(self) -> dict:
        """加载并解密数据"""
        if not self.is_authenticated:
            raise PermissionError("请先进行身份验证")
        
        try:
            with open(self.data_file, 'r', encoding='utf-8') as f:
                encrypted_data = json.load(f)
            
            decrypted_json = self.crypto.decrypt(encrypted_data, self.master_password)
            return json.loads(decrypted_json)
        except Exception as e:
            raise Exception(f"加载数据失败: {e}")
    
    def _save_data(self, data: dict):
        """加密并保存数据"""
        if not self.is_authenticated:
            raise PermissionError("请先进行身份验证")
        
        try:
            data['last_modified'] = datetime.now().isoformat()
            encrypted_data = self.crypto.encrypt(json.dumps(data), self.master_password)
            
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(encrypted_data, f, indent=2)
            
            # 创建备份
            if self.config.get('backup_enabled', True):
                self._create_backup()
        except Exception as e:
            raise Exception(f"保存数据失败: {e}")
    
    def _create_backup(self):
        """创建数据备份"""
        try:
            if os.path.exists(self.data_file):
                backup_file = f"{self.data_file}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
                import shutil
                shutil.copy2(self.data_file, backup_file)
                
                # 清理旧的备份文件(保留最近5个)
                backup_files = [f for f in os.listdir('.') if f.startswith(f"{self.data_file}.backup.")]
                backup_files.sort(reverse=True)
                for old_backup in backup_files[5:]:
                    os.remove(old_backup)
        except Exception as e:
            print(f"创建备份失败: {e}")
    
    def add_password(self, service: str, username: str, password: str, notes: str = "") -> bool:
        """添加密码条目"""
        try:
            data = self._load_data()
            
            # 检查是否已存在相同服务
            for entry in data['entries']:
                if entry['service'].lower() == service.lower() and entry['username'] == username:
                    raise ValueError(f"服务 '{service}' 的用户名 '{username}' 已存在")
            
            new_entry = {
                'id': len(data['entries']) + 1,
                'service': service,
                'username': username,
                'password': password,
                'notes': notes,
                'created_at': datetime.now().isoformat(),
                'updated_at': datetime.now().isoformat()
            }
            
            data['entries'].append(new_entry)
            self._save_data(data)
            return True
        except Exception as e:
            raise Exception(f"添加密码失败: {e}")
    
    def get_password(self, service: str, username: str = None) -> dict:
        """获取密码条目"""
        try:
            data = self._load_data()
            
            if username:
                # 获取特定服务的特定用户名
                for entry in data['entries']:
                    if entry['service'].lower() == service.lower() and entry['username'] == username:
                        return entry
                raise ValueError(f"未找到服务 '{service}' 的用户名 '{username}'")
            else:
                # 获取特定服务的所有条目
                entries = [entry for entry in data['entries'] if entry['service'].lower() == service.lower()]
                if not entries:
                    raise ValueError(f"未找到服务 '{service}' 的密码条目")
                return entries
        except Exception as e:
            raise Exception(f"获取密码失败: {e}")
    
    def list_services(self) -> list:
        """列出所有服务"""
        try:
            data = self._load_data()
            services = {}
            for entry in data['entries']:
                service = entry['service']
                if service not in services:
                    services[service] = 0
                services[service] += 1
            
            return [{"service": service, "count": count} for service, count in services.items()]
        except Exception as e:
            raise Exception(f"列出服务失败: {e}")
    
    def update_password(self, service: str, username: str, new_password: str, new_notes: str = None) -> bool:
        """更新密码"""
        try:
            data = self._load_data()
            
            for entry in data['entries']:
                if entry['service'].lower() == service.lower() and entry['username'] == username:
                    entry['password'] = new_password
                    if new_notes is not None:
                        entry['notes'] = new_notes
                    entry['updated_at'] = datetime.now().isoformat()
                    
                    self._save_data(data)
                    return True
            
            raise ValueError(f"未找到服务 '{service}' 的用户名 '{username}'")
        except Exception as e:
            raise Exception(f"更新密码失败: {e}")
    
    def delete_password(self, service: str, username: str) -> bool:
        """删除密码条目"""
        try:
            data = self._load_data()
            
            for i, entry in enumerate(data['entries']):
                if entry['service'].lower() == service.lower() and entry['username'] == username:
                    data['entries'].pop(i)
                    self._save_data(data)
                    return True
            
            raise ValueError(f"未找到服务 '{service}' 的用户名 '{username}'")
        except Exception as e:
            raise Exception(f"删除密码失败: {e}")
    
    def search_passwords(self, keyword: str) -> list:
        """搜索密码条目"""
        try:
            data = self._load_data()
            keyword = keyword.lower()
            
            results = []
            for entry in data['entries']:
                if (keyword in entry['service'].lower() or 
                    keyword in entry['username'].lower() or 
                    keyword in entry['notes'].lower()):
                    results.append(entry)
            
            return results
        except Exception as e:
            raise Exception(f"搜索密码失败: {e}")
    
    def export_data(self, export_file: str) -> bool:
        """导出数据(未加密)"""
        try:
            data = self._load_data()
            
            # 移除敏感的时间戳信息
            export_data = {
                "version": data["version"],
                "exported_at": datetime.now().isoformat(),
                "entries": []
            }
            
            for entry in data["entries"]:
                export_data["entries"].append({
                    "service": entry["service"],
                    "username": entry["username"],
                    "password": entry["password"],
                    "notes": entry["notes"]
                })
            
            with open(export_file, 'w', encoding='utf-8') as f:
                json.dump(export_data, f, indent=2, ensure_ascii=False)
            
            return True
        except Exception as e:
            raise Exception(f"导出数据失败: {e}")
    
    def get_stats(self) -> dict:
        """获取统计信息"""
        try:
            data = self._load_data()
            
            total_entries = len(data['entries'])
            services_count = len(set(entry['service'] for entry in data['entries']))
            
            # 检查弱密码
            weak_passwords = 0
            for entry in data['entries']:
                strength = self.generator.check_strength(entry['password'])
                if strength['strength'] == '弱':
                    weak_passwords += 1
            
            return {
                'total_entries': total_entries,
                'services_count': services_count,
                'weak_passwords': weak_passwords,
                'created_at': data['created_at'],
                'last_modified': data['last_modified']
            }
        except Exception as e:
            raise Exception(f"获取统计信息失败: {e}")
    
    def lock(self):
        """锁定密码管理器"""
        self.is_authenticated = False
        self.master_password = None
        print("密码管理器已锁定")

# 测试密码管理器
if __name__ == "__main__":
    manager = PasswordManager()
    
    # 测试初始化
    test_password = "MyStrongMasterPassword123!"
    if manager.authenticate(test_password):
        print("身份验证成功")
        
        # 添加测试数据
        manager.add_password("gmail.com", "user@gmail.com", "password123", "个人邮箱")
        manager.add_password("github.com", "developer", "github_pass", "代码仓库")
        
        # 列出服务
        services = manager.list_services()
        print("服务列表:", services)
        
        # 搜索密码
        results = manager.search_passwords("gmail")
        print("搜索结果:", results)
        
        # 获取统计信息
        stats = manager.get_stats()
        print("统计信息:", stats)
        
        manager.lock()
    else:
        print("身份验证失败")

4. gui.py - 图形界面

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import pyperclip
from password_manager import PasswordManager
from crypto import PasswordGenerator

class PasswordManagerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("密码管理器 - 安全存储您的密码")
        self.root.geometry("800x600")
        self.root.configure(bg='#f0f0f0')
        
        self.manager = PasswordManager()
        self.generator = PasswordGenerator()
        
        # 创建样式
        self.setup_styles()
        
        # 创建界面
        self.create_login_screen()
    
    def setup_styles(self):
        """设置界面样式"""
        style = ttk.Style()
        style.configure('Title.TLabel', font=('Arial', 16, 'bold'), background='#f0f0f0')
        style.configure('Header.TLabel', font=('Arial', 12, 'bold'), background='#f0f0f0')
        style.configure('Success.TLabel', foreground='green', background='#f0f0f0')
        style.configure('Warning.TLabel', foreground='orange', background='#f0f0f0')
        style.configure('Error.TLabel', foreground='red', background='#f0f0f0')
    
    def clear_window(self):
        """清空窗口内容"""
        for widget in self.root.winfo_children():
            widget.destroy()
    
    def create_login_screen(self):
        """创建登录界面"""
        self.clear_window()
        
        # 主框架
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题
        title_label = ttk.Label(main_frame, text="🔐 密码管理器", style='Title.TLabel')
        title_label.pack(pady=20)
        
        # 说明文字
        desc_label = ttk.Label(main_frame, text="安全地存储和管理您的密码", 
                              font=('Arial', 10), background='#f0f0f0')
        desc_label.pack(pady=10)
        
        # 登录框架
        login_frame = ttk.LabelFrame(main_frame, text="身份验证", padding="15")
        login_frame.pack(pady=20, fill=tk.X)
        
        # 密码输入
        ttk.Label(login_frame, text="主密码:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.master_password_var = tk.StringVar()
        password_entry = ttk.Entry(login_frame, textvariable=self.master_password_var, 
                                  show="•", width=30)
        password_entry.grid(row=0, column=1, pady=5, padx=5)
        password_entry.bind('<Return>', lambda e: self.authenticate())
        
        # 显示密码复选框
        self.show_password_var = tk.BooleanVar()
        show_password_cb = ttk.Checkbutton(login_frame, text="显示密码", 
                                          variable=self.show_password_var,
                                          command=lambda: self.toggle_password_visibility(password_entry))
        show_password_cb.grid(row=1, column=1, sticky=tk.W, pady=5)
        
        # 按钮框架
        button_frame = ttk.Frame(login_frame)
        button_frame.grid(row=2, column=0, columnspan=2, pady=15)
        
        ttk.Button(button_frame, text="登录", 
                  command=self.authenticate).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="初始化新数据库", 
                  command=self.initialize_new_db).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="退出", 
                  command=self.root.quit).pack(side=tk.LEFT, padx=5)
        
        # 密码强度提示
        self.strength_label = ttk.Label(login_frame, text="", style='Warning.TLabel')
        self.strength_label.grid(row=3, column=0, columnspan=2, pady=5)
        
        # 绑定密码强度检查
        self.master_password_var.trace('w', self.check_password_strength)
    
    def toggle_password_visibility(self, entry):
        """切换密码显示/隐藏"""
        if self.show_password_var.get():
            entry.config(show="")
        else:
            entry.config(show="•")
    
    def check_password_strength(self, *args):
        """检查密码强度"""
        password = self.master_password_var.get()
        if password:
            strength_info = self.generator.check_strength(password)
            self.strength_label.config(
                text=f"密码强度: {strength_info['strength']}",
                style=f"{strength_info['strength'].title()}.TLabel"
            )
        else:
            self.strength_label.config(text="")
    
    def authenticate(self):
        """身份验证"""
        password = self.master_password_var.get()
        if not password:
            messagebox.showerror("错误", "请输入主密码")
            return
        
        try:
            if self.manager.authenticate(password):
                self.create_main_screen()
            else:
                messagebox.showerror("错误", "密码错误或数据库损坏")
        except Exception as e:
            messagebox.showerror("错误", f"认证失败: {str(e)}")
    
    def initialize_new_db(self):
        """初始化新数据库"""
        password = self.master_password_var.get()
        if not password:
            messagebox.showerror("错误", "请输入主密码")
            return
        
        # 检查密码强度
        if not self.manager.crypto.verify_password(password):
            messagebox.showerror("错误", 
                "密码强度不足!\n请使用至少8位字符,包含大小写字母、数字和特殊字符")
            return
        
        try:
            if messagebox.askyesno("确认", "这将创建新的密码数据库,确定继续吗?"):
                self.manager.initialize(password)
                messagebox.showinfo("成功", "密码管理器初始化成功!")
                self.create_main_screen()
        except Exception as e:
            messagebox.showerror("错误", f"初始化失败: {str(e)}")
    
    def create_main_screen(self):
        """创建主界面"""
        self.clear_window()
        
        # 创建菜单栏
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="导出数据", command=self.export_data)
        file_menu.add_separator()
        file_menu.add_command(label="锁定", command=self.lock_app)
        file_menu.add_command(label="退出", command=self.root.quit)
        
        # 工具菜单
        tools_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="工具", menu=tools_menu)
        tools_menu.add_command(label="密码生成器", command=self.show_password_generator)
        tools_menu.add_command(label="统计信息", command=self.show_stats)
        
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 顶部按钮框架
        top_frame = ttk.Frame(main_frame)
        top_frame.pack(fill=tk.X, pady=10)
        
        ttk.Button(top_frame, text="➕ 添加密码", 
                  command=self.add_password_dialog).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="🔍 搜索", 
                  command=self.search_dialog).pack(side=tk.LEFT, padx=5)
        ttk.Button(top_frame, text="📊 刷新", 
                  command=self.refresh_list).pack(side=tk.LEFT, padx=5)
        
        # 搜索框架
        search_frame = ttk.Frame(main_frame)
        search_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(search_frame, text="快速搜索:").pack(side=tk.LEFT)
        self.search_var = tk.StringVar()
        search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
        search_entry.pack(side=tk.LEFT, padx=5)
        search_entry.bind('<KeyRelease>', self.quick_search)
        
        # 密码列表
        list_frame = ttk.LabelFrame(main_frame, text="存储的密码", padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # 创建树形视图
        columns = ('service', 'username', 'updated')
        self.tree = ttk.Treeview(list_frame, columns=columns, show='headings')
        
        # 定义列
        self.tree.heading('service', text='服务')
        self.tree.heading('username', text='用户名')
        self.tree.heading('updated', text='更新时间')
        
        self.tree.column('service', width=200)
        self.tree.column('username', width=200)
        self.tree.column('updated', width=150)
        
        # 滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 绑定双击事件
        self.tree.bind('<Double-1>', self.on_item_double_click)
        
        # 底部按钮框架
        bottom_frame = ttk.Frame(main_frame)
        bottom_frame.pack(fill=tk.X, pady=10)
        
        ttk.Button(bottom_frame, text="查看详情", 
                  command=self.view_password_details).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_frame, text="复制密码", 
                  command=self.copy_password).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_frame, text="编辑", 
                  command=self.edit_password_dialog).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_frame, text="删除", 
                  command=self.delete_password_dialog).pack(side=tk.LEFT, padx=5)
        
        # 加载数据
        self.refresh_list()
    
    def refresh_list(self):
        """刷新密码列表"""
        try:
            # 清空现有数据
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # 获取所有服务
            services = self.manager.list_services()
            for service_info in services:
                service = service_info['service']
                entries = self.manager.get_password(service)
                
                if isinstance(entries, dict):
                    # 单个条目
                    entries = [entries]
                
                for entry in entries:
                    self.tree.insert('', tk.END, values=(
                        entry['service'],
                        entry['username'],
                        entry['updated_at'][:19]  # 只显示日期和时间部分
                    ))
        
        except Exception as e:
            messagebox.showerror("错误", f"刷新列表失败: {str(e)}")
    
    def quick_search(self, event=None):
        """快速搜索"""
        keyword = self.search_var.get().lower()
        if not keyword:
            self.refresh_list()
            return
        
        try:
            # 清空现有数据
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # 搜索并显示结果
            results = self.manager.search_passwords(keyword)
            for entry in results:
                self.tree.insert('', tk.END, values=(
                    entry['service'],
                    entry['username'],
                    entry['updated_at'][:19]
                ))
        
        except Exception as e:
            messagebox.showerror("错误", f"搜索失败: {str(e)}")
    
    def get_selected_entry(self):
        """获取选中的条目"""
        selection = self.tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择一个条目")
            return None
        
        item = selection[0]
        values = self.tree.item(item, 'values')
        service, username, _ = values
        
        try:
            return self.manager.get_password(service, username)
        except Exception as e:
            messagebox.showerror("错误", f"获取条目失败: {str(e)}")
            return None
    
    def on_item_double_click(self, event):
        """双击条目事件"""
        self.view_password_details()
    
    def view_password_details(self):
        """查看密码详情"""
        entry = self.get_selected_entry()
        if not entry:
            return
        
        # 创建详情对话框
        detail_window = tk.Toplevel(self.root)
        detail_window.title("密码详情")
        detail_window.geometry("400x300")
        detail_window.transient(self.root)
        detail_window.grab_set()
        
        # 创建框架
        frame = ttk.Frame(detail_window, padding="15")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # 显示详情
        ttk.Label(frame, text="服务:", style='Header.TLabel').grid(row=0, column=0, sticky=tk.W, pady=5)
        ttk.Label(frame, text=entry['service']).grid(row=0, column=1, sticky=tk.W, pady=5)
        
        ttk.Label(frame, text="用户名:", style='Header.TLabel').grid(row=1, column=0, sticky=tk.W, pady=5)
        ttk.Label(frame, text=entry['username']).grid(row=1, column=1, sticky=tk.W, pady=5)
        
        ttk.Label(frame, text="密码:", style='Header.TLabel').grid(row=2, column=0, sticky=tk.W, pady=5)
        
        # 密码显示框
        password_frame = ttk.Frame(frame)
        password_frame.grid(row=2, column=1, sticky=tk.W, pady=5)
        
        password_var = tk.StringVar(value="•" * len(entry['password']))
        password_label = ttk.Label(password_frame, textvariable=password_var)
        password_label.pack(side=tk.LEFT)
        
        def toggle_password_display():
            if password_var.get().startswith('•'):
                password_var.set(entry['password'])
            else:
                password_var.set("•" * len(entry['password']))
        
        ttk.Button(password_frame, text="显示/隐藏", 
                  command=toggle_password_display).pack(side=tk.LEFT, padx=5)
        
        ttk.Button(password_frame, text="复制", 
                  command=lambda: pyperclip.copy(entry['password'])).pack(side=tk.LEFT, padx=5)
        
        # 备注
        ttk.Label(frame, text="备注:", style='Header.TLabel').grid(row=3, column=0, sticky=tk.W, pady=5)
        notes_text = tk.Text(frame, height=5, width=30)
        notes_text.grid(row=3, column=1, sticky=tk.W+tk.E, pady=5)
        notes_text.insert('1.0', entry.get('notes', ''))
        notes_text.config(state=tk.DISABLED)
        
        # 时间信息
        ttk.Label(frame, text="创建时间:", style='Header.TLabel').grid(row=4, column=0, sticky=tk.W, pady=5)
        ttk.Label(frame, text=entry['created_at'][:19]).grid(row=4, column=1, sticky=tk.W, pady=5)
        
        ttk.Label(frame, text="更新时间:", style='Header.TLabel').grid(row=5, column=0, sticky=tk.W, pady=5)
        ttk.Label(frame, text=entry['updated_at'][:19]).grid(row=5, column=1, sticky=tk.W, pady=5)
        
        # 关闭按钮
        ttk.Button(frame, text="关闭", 
                  command=detail_window.destroy).grid(row=6, column=1, sticky=tk.E, pady=15)
    
    def copy_password(self):
        """复制密码到剪贴板"""
        entry = self.get_selected_entry()
        if entry:
            pyperclip.copy(entry['password'])
            messagebox.showinfo("成功", "密码已复制到剪贴板")
    
    def add_password_dialog(self):
        """添加密码对话框"""
        self._show_password_editor()
    
    def edit_password_dialog(self):
        """编辑密码对话框"""
        entry = self.get_selected_entry()
        if entry:
            self._show_password_editor(entry)
    
    def _show_password_editor(self, entry=None):
        """显示密码编辑器"""
        editor_window = tk.Toplevel(self.root)
        editor_window.title("编辑密码" if entry else "添加密码")
        editor_window.geometry("400x350")
        editor_window.transient(self.root)
        editor_window.grab_set()
        
        # 创建框架
        frame = ttk.Frame(editor_window, padding="15")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # 服务名称
        ttk.Label(frame, text="服务名称:*").grid(row=0, column=0, sticky=tk.W, pady=5)
        service_var = tk.StringVar(value=entry['service'] if entry else "")
        service_entry = ttk.Entry(frame, textvariable=service_var, width=30)
        service_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
        
        # 用户名
        ttk.Label(frame, text="用户名:*").grid(row=1, column=0, sticky=tk.W, pady=5)
        username_var = tk.StringVar(value=entry['username'] if entry else "")
        username_entry = ttk.Entry(frame, textvariable=username_var, width=30)
        username_entry.grid(row=1, column=1, sticky=tk.W, pady=5)
        
        # 密码
        ttk.Label(frame, text="密码:*").grid(row=2, column=0, sticky=tk.W, pady=5)
        password_frame = ttk.Frame(frame)
        password_frame.grid(row=2, column=1, sticky=tk.W, pady=5)
        
        password_var = tk.StringVar(value=entry['password'] if entry else "")
        password_entry = ttk.Entry(password_frame, textvariable=password_var, width=25, show="•")
        password_entry.pack(side=tk.LEFT)
        
        def toggle_password_visibility():
            if password_entry.cget('show') == "•":
                password_entry.config(show="")
            else:
                password_entry.config(show="•")
        
        ttk.Button(password_frame, text="👁", width=3,
                  command=toggle_password_visibility).pack(side=tk.LEFT, padx=2)
        
        ttk.Button(password_frame, text="🎲", width=3,
                  command=self.show_password_generator).pack(side=tk.LEFT, padx=2)
        
        # 密码强度
        strength_label = ttk.Label(frame, text="")
        strength_label.grid(row=3, column=1, sticky=tk.W)
        
        def check_strength(*args):
            strength_info = self.generator.check_strength(password_var.get())
            strength_label.config(
                text=f"强度: {strength_info['strength']}",
                style=f"{strength_info['strength'].title()}.TLabel"
            )
        
        password_var.trace('w', check_strength)
        if entry:
            check_strength()
        
        # 备注
        ttk.Label(frame, text="备注:").grid(row=4, column=0, sticky=tk.W+tk.N, pady=5)
        notes_text = tk.Text(frame, height=5, width=30)
        notes_text.grid(row=4, column=1, sticky=tk.W+tk.E, pady=5)
        if entry and 'notes' in entry:
            notes_text.insert('1.0', entry['notes'])
        
        # 按钮框架
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=5, column=0, columnspan=2, pady=15)
        
        def save_password():
            service = service_var.get().strip()
            username = username_var.get().strip()
            password = password_var.get()
            notes = notes_text.get('1.0', tk.END).strip()
            
            if not service or not username or not password:
                messagebox.showerror("错误", "请填写所有必填字段(带*号)")
                return
            
            try:
                if entry:
                    # 更新现有条目
                    self.manager.update_password(entry['service'], entry['username'], 
                                               password, notes)
                    messagebox.showinfo("成功", "密码更新成功!")
                else:
                    # 添加新条目
                    self.manager.add_password(service, username, password, notes)
                    messagebox.showinfo("成功", "密码添加成功!")
                
                editor_window.destroy()
                self.refresh_list()
            
            except Exception as e:
                messagebox.showerror("错误", f"保存失败: {str(e)}")
        
        ttk.Button(button_frame, text="保存", 
                  command=save_password).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="取消", 
                  command=editor_window.destroy).pack(side=tk.LEFT, padx=5)
    
    def delete_password_dialog(self):
        """删除密码对话框"""
        entry = self.get_selected_entry()
        if not entry:
            return
        
        if messagebox.askyesno("确认删除", 
                              f"确定要删除 {entry['service']}{entry['username']} 吗?"):
            try:
                self.manager.delete_password(entry['service'], entry['username'])
                messagebox.showinfo("成功", "密码删除成功!")
                self.refresh_list()
            except Exception as e:
                messagebox.showerror("错误", f"删除失败: {str(e)}")
    
    def search_dialog(self):
        """搜索对话框"""
        keyword = simpledialog.askstring("搜索", "请输入搜索关键词:")
        if keyword:
            self.search_var.set(keyword)
            self.quick_search()
    
    def show_password_generator(self):
        """显示密码生成器"""
        generator_window = tk.Toplevel(self.root)
        generator_window.title("密码生成器")
        generator_window.geometry("400x300")
        generator_window.transient(self.root)
        generator_window.grab_set()
        
        frame = ttk.Frame(generator_window, padding="15")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # 密码长度
        ttk.Label(frame, text="密码长度:").grid(row=0, column=0, sticky=tk.W, pady=5)
        length_var = tk.IntVar(value=16)
        length_scale = ttk.Scale(frame, from_=8, to=32, variable=length_var, orient=tk.HORIZONTAL)
        length_scale.grid(row=0, column=1, sticky=tk.W+tk.E, pady=5)
        length_label = ttk.Label(frame, textvariable=length_var)
        length_label.grid(row=0, column=2, padx=5)
        
        # 字符类型选项
        ttk.Label(frame, text="字符类型:").grid(row=1, column=0, sticky=tk.W+tk.N, pady=5)
        options_frame = ttk.Frame(frame)
        options_frame.grid(row=1, column=1, columnspan=2, sticky=tk.W, pady=5)
        
        upper_var = tk.BooleanVar(value=True)
        lower_var = tk.BooleanVar(value=True)
        digits_var = tk.BooleanVar(value=True)
        special_var = tk.BooleanVar(value=True)
        
        ttk.Checkbutton(options_frame, text="大写字母", variable=upper_var).pack(anchor=tk.W)
        ttk.Checkbutton(options_frame, text="小写字母", variable=lower_var).pack(anchor=tk.W)
        ttk.Checkbutton(options_frame, text="数字", variable=digits_var).pack(anchor=tk.W)
        ttk.Checkbutton(options_frame, text="特殊字符", variable=special_var).pack(anchor=tk.W)
        
        # 生成的密码
        ttk.Label(frame, text="生成的密码:").grid(row=2, column=0, sticky=tk.W, pady=10)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(frame, textvariable=password_var, width=30, state='readonly')
        password_entry.grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E, pady=10)
        
        # 密码强度
        strength_label = ttk.Label(frame, text="")
        strength_label.grid(row=3, column=1, columnspan=2, sticky=tk.W)
        
        def generate_password():
            try:
                password = self.generator.generate_password(
                    length=length_var.get(),
                    use_upper=upper_var.get(),
                    use_lower=lower_var.get(),
                    use_digits=digits_var.get(),
                    use_special=special_var.get()
                )
                password_var.set(password)
                
                # 显示强度
                strength_info = self.generator.check_strength(password)
                strength_label.config(
                    text=f"密码强度: {strength_info['strength']}",
                    style=f"{strength_info['strength'].title()}.TLabel"
                )
            
            except ValueError as e:
                messagebox.showerror("错误", str(e))
        
        def use_password():
            if password_var.get():
                generator_window.destroy()
                return password_var.get()
            return None
        
        # 按钮框架
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=4, column=0, columnspan=3, pady=15)
        
        ttk.Button(button_frame, text="生成密码", 
                  command=generate_password).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="使用此密码", 
                  command=use_password).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="复制", 
                  command=lambda: pyperclip.copy(password_var.get())).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="关闭", 
                  command=generator_window.destroy).pack(side=tk.LEFT, padx=5)
        
        # 初始生成一个密码
        generate_password()
    
    def export_data(self):
        """导出数据"""
        filename = simpledialog.askstring("导出数据", "请输入导出文件名:", initialvalue="passwords_export.json")
        if filename:
            try:
                if self.manager.export_data(filename):
                    messagebox.showinfo("成功", f"数据已导出到 {filename}")
                else:
                    messagebox.showerror("错误", "导出失败")
            except Exception as e:
                messagebox.showerror("错误", f"导出失败: {str(e)}")
    
    def show_stats(self):
        """显示统计信息"""
        try:
            stats = self.manager.get_stats()
            
            stats_window = tk.Toplevel(self.root)
            stats_window.title("统计信息")
            stats_window.geometry("300x250")
            stats_window.transient(self.root)
            stats_window.grab_set()
            
            frame = ttk.Frame(stats_window, padding="15")
            frame.pack(fill=tk.BOTH, expand=True)
            
            ttk.Label(frame, text="📊 密码管理器统计", style='Title.TLabel').pack(pady=10)
            
            info_text = f"""
总密码条目: {stats['total_entries']}
服务数量: {stats['services_count']}
弱密码数量: {stats['weak_passwords']}

创建时间: {stats['created_at'][:10]}
最后修改: {stats['last_modified'][:10]}
            """
            
            ttk.Label(frame, text=info_text.strip(), justify=tk.LEFT).pack(pady=10)
            
            ttk.Button(frame, text="关闭", 
                      command=stats_window.destroy).pack(pady=10)
        
        except Exception as e:
            messagebox.showerror("错误", f"获取统计信息失败: {str(e)}")
    
    def lock_app(self):
        """锁定应用"""
        self.manager.lock()
        self.create_login_screen()

def main():
    """主函数"""
    root = tk.Tk()
    app = PasswordManagerGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()

5. main.py - 主程序入口

#!/usr/bin/env python3
"""
密码管理器 - 本地版
一个安全的本地密码管理工具,使用AES加密保护您的密码。
"""

import sys
import os
from gui import main as gui_main

def main():
    """主程序入口"""
    print("=" * 50)
    print("       🔐 密码管理器 - 本地版")
    print("=" * 50)
    print("一个安全的本地密码管理工具")
    print("使用AES加密技术保护您的敏感数据")
    print()
    
    # 检查依赖
    try:
        import tkinter
        import cryptography
        import pyperclip
    except ImportError as e:
        print(f"错误: 缺少必要的依赖库 - {e}")
        print("请运行: pip install -r requirements.txt")
        input("按回车键退出...")
        return
    
    # 启动图形界面
    try:
        gui_main()
    except Exception as e:
        print(f"程序运行出错: {e}")
        input("按回车键退出...")

if __name__ == "__main__":
    main()

如何在VSCode中运行

1. 环境设置

  1. 创建项目文件夹

    mkdir password-manager
    cd password-manager
    
  2. 创建虚拟环境(推荐):

    python -m venv password_env
    # Windows
    password_env\Scripts\activate
    # Mac/Linux
    source password_env/bin/activate
    
  3. 安装依赖

    pip install -r requirements.txt
    

2. 运行程序

在VSCode中,有几种方式运行程序:

方法一:运行主程序(推荐)

python main.py

方法二:运行图形界面

python gui.py

方法三:在VSCode中使用运行按钮

  • 打开main.py文件
  • 点击右上角的运行按钮 ▶️
  • 或按F5键

3. 使用说明

首次使用
  1. 程序启动后会显示登录界面
  2. 输入一个强主密码(至少8位,包含大小写字母、数字和特殊字符)
  3. 点击"初始化新数据库"
  4. 系统会创建加密的密码数据库
主要功能
  • 添加密码:存储新的账号密码
  • 查看密码:查看已保存的密码详情
  • 编辑密码:修改现有的密码条目
  • 删除密码:删除不需要的密码条目
  • 密码生成器:生成强随机密码
  • 搜索功能:快速查找密码
  • 数据导出:导出未加密的密码数据(谨慎使用)
  • 统计信息:查看密码管理器使用情况

安全特性

  1. AES加密:使用行业标准的AES加密算法
  2. 密码派生:使用PBKDF2从主密码派生加密密钥
  3. 盐值随机化:每次加密使用不同的盐值
  4. 本地存储:所有数据仅存储在本地
  5. 自动备份:自动创建数据备份
  6. 密码强度检查:确保使用强密码

学习要点

这个项目涵盖了以下编程概念:

  1. 加密技术:AES加密、密钥派生、盐值使用
  2. 文件操作:JSON文件读写、数据序列化
  3. 图形界面:使用Tkinter创建GUI应用
  4. 异常处理:完善的错误处理机制
  5. 数据验证:输入验证和密码强度检查
  6. 面向对象编程:类的设计和封装

扩展建议

学生可以进一步扩展这个项目:

  1. 云同步:添加安全的云存储同步功能
  2. 浏览器扩展:开发浏览器插件自动填充密码
  3. 双重认证:添加2FA支持
  4. 密码泄露检查:集成Have I Been Pwned API
  5. 移动端应用:使用Kivy或Flutter开发移动版本
  6. 生物识别:添加指纹或面部识别支持

重要安全提醒

⚠️ 安全注意事项

  • 主密码是保护所有密码的关键,请务必使用强密码
  • 定期备份密码数据库文件
  • 不要在公共计算机上使用此程序
  • 考虑使用物理安全密钥进行额外保护
  • 此程序为教学项目,生产环境请使用专业密码管理器

这个项目是一个很好的起点,可以帮助学生学习密码学基础、数据安全和应用程序开发。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值