使用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. 环境设置
-
创建项目文件夹:
mkdir password-manager cd password-manager -
创建虚拟环境(推荐):
python -m venv password_env # Windows password_env\Scripts\activate # Mac/Linux source password_env/bin/activate -
安装依赖:
pip install -r requirements.txt
2. 运行程序
在VSCode中,有几种方式运行程序:
方法一:运行主程序(推荐)
python main.py
方法二:运行图形界面
python gui.py
方法三:在VSCode中使用运行按钮
- 打开
main.py文件 - 点击右上角的运行按钮 ▶️
- 或按F5键
3. 使用说明
首次使用
- 程序启动后会显示登录界面
- 输入一个强主密码(至少8位,包含大小写字母、数字和特殊字符)
- 点击"初始化新数据库"
- 系统会创建加密的密码数据库
主要功能
- 添加密码:存储新的账号密码
- 查看密码:查看已保存的密码详情
- 编辑密码:修改现有的密码条目
- 删除密码:删除不需要的密码条目
- 密码生成器:生成强随机密码
- 搜索功能:快速查找密码
- 数据导出:导出未加密的密码数据(谨慎使用)
- 统计信息:查看密码管理器使用情况
安全特性
- AES加密:使用行业标准的AES加密算法
- 密码派生:使用PBKDF2从主密码派生加密密钥
- 盐值随机化:每次加密使用不同的盐值
- 本地存储:所有数据仅存储在本地
- 自动备份:自动创建数据备份
- 密码强度检查:确保使用强密码
学习要点
这个项目涵盖了以下编程概念:
- 加密技术:AES加密、密钥派生、盐值使用
- 文件操作:JSON文件读写、数据序列化
- 图形界面:使用Tkinter创建GUI应用
- 异常处理:完善的错误处理机制
- 数据验证:输入验证和密码强度检查
- 面向对象编程:类的设计和封装
扩展建议
学生可以进一步扩展这个项目:
- 云同步:添加安全的云存储同步功能
- 浏览器扩展:开发浏览器插件自动填充密码
- 双重认证:添加2FA支持
- 密码泄露检查:集成Have I Been Pwned API
- 移动端应用:使用Kivy或Flutter开发移动版本
- 生物识别:添加指纹或面部识别支持
重要安全提醒
⚠️ 安全注意事项:
- 主密码是保护所有密码的关键,请务必使用强密码
- 定期备份密码数据库文件
- 不要在公共计算机上使用此程序
- 考虑使用物理安全密钥进行额外保护
- 此程序为教学项目,生产环境请使用专业密码管理器
这个项目是一个很好的起点,可以帮助学生学习密码学基础、数据安全和应用程序开发。

1609

被折叠的 条评论
为什么被折叠?



