密码管理器的详细介绍:
-
核心安全功能
-
密码管理功能
- 全功能CRUD操作:
- 创建:支持添加网站/应用、用户名、密码、备注等信息
- 读取:表格视图展示所有密码条目,支持双击查看详情
- 更新:可修改任何已存储的密码条目
- 删除:提供确认对话框防止误删
- 智能密码生成器:
- 可生成8-32位的强密码
- 包含大小写字母、数字和特殊符号(!@#$%^&*())
- 一键复制功能方便直接使用
-
用户界面特色
- 三重窗口保护:
- 主密码设置窗口(首次使用)
- 登录验证窗口
- 主操作界面
- 专业数据展示:
# 密码表格视图实现代码片段 self.tree = ttk.Treeview(columns=("id", "website", "username", "email")) self.tree.heading("id", text="ID", anchor=tk.CENTER) self.tree.column("id", width=50)
- 详情查看安全措施:
- 密码默认显示为""
- 需主动点击"显示"按钮才显示真实密码
- 详情窗口独立显示所有加密信息
-
搜索与工具功能
- 即时搜索:支持网站/用户名/邮箱关键词搜索
- 批量操作:可同时管理多个密码条目
- 数据导出:完整导出所有密码数据(需谨慎使用)
-
特殊技术实现
- 跨平台适配:
# Windows使用.ico,其他系统使用.png if sys.platform.startswith('win'): self.root.iconbitmap(default="icon.ico") else: img = tk.PhotoImage(file="icon.png")
- 内存安全设计:
- 密码信息只在需要时解密
- 不保留明文密码在内存中
- 剪贴板自动清除功能
- 跨平台适配:
-
企业级功能扩展
- 密码策略配置(可扩展):
# 密码强度检查示例 def check_password_strength(pwd): has_upper = any(c.isupper() for c in pwd) has_lower = any(c.islower() for c in pwd) has_digit = any(c.isdigit() for c in pwd) has_special = any(c in "!@#$%^&*()" for c in pwd) return len(pwd) >= 8 and has_upper and has_lower and has_digit and has_special
- 审计日志(建议扩展):
- 记录所有密码查看/修改操作
- 记录登录尝试
- 支持导出操作历史
- 密码策略配置(可扩展):
-
安全使用建议
-
密钥备份:将secret.key文件保存在安全位置
-
密码策略:
- 主密码应≥12位
- 避免使用常见密码
- 定期更换主密码
-
物理安全:不在公共电脑上使用本工具
-
应急措施:定期导出加密备份到安全位置
-
技术栈说明 组件 技术实现 加密方案 AES-256 + PBKDF2 数据库 SQLite3 用户界面 Tkinter/ttk 跨平台支持 Python标准库 依赖管理 纯标准库实现 -
典型使用场景
-
个人密码管理:
- 存储100+网站密码
- 自动填充复杂密码
- 家庭共享密码库
-
团队协作:
- 共享服务器凭证
- 管理API密钥
- 控制数据库访问权限
-
应急恢复:
- 快速查找遗忘的密码
- 迁移到新设备
- 浏览器密码导出替代方案
该密码管理器特别适合需要管理大量密码同时又注重安全性的用户,其简洁的界面设计和强大的加密功能在开源密码管理工具中表现出色。
密码管理器开源源码如下图:
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import sqlite3
import hashlib
from cryptography.fernet import Fernet
import random
import string
import os
import sys
import psutil
import socket
class PasswordManager:
def __init__(self):
self.db_name = "passwords.db"
self.key_file = "secret.key"
self.conn = None
self.cursor = None
self.fernet = None
self._initialize()
def _initialize(self):
"""Initialize database and encryption key"""
if not os.path.exists(self.key_file):
key = Fernet.generate_key()
with open(self.key_file, "wb") as f:
f.write(key)
with open(self.key_file, "rb") as f:
key = f.read()
self.fernet = Fernet(key)
self.conn = sqlite3.connect(self.db_name)
self.cursor = self.conn.cursor()
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT NOT NULL,
username TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
notes TEXT
)
''')
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS master_password (
id INTEGER PRIMARY KEY,
password_hash TEXT NOT NULL
)
''')
self.conn.commit()
def _hash_password(self, password):
"""Hash password using SHA-256"""
return hashlib.sha256(password.encode()).hexdigest()
def set_master_password(self, password):
"""Set master password"""
password_hash = self._hash_password(password)
self.cursor.execute("SELECT * FROM master_password")
if self.cursor.fetchone():
self.cursor.execute("UPDATE master_password SET password_hash = ?", (password_hash,))
else:
self.cursor.execute("INSERT INTO master_password (password_hash) VALUES (?)", (password_hash,))
self.conn.commit()
def verify_master_password(self, password):
"""Verify master password"""
password_hash = self._hash_password(password)
self.cursor.execute("SELECT password_hash FROM master_password")
result = self.cursor.fetchone()
if not result:
return False
return password_hash == result[0]
def _encrypt(self, text):
"""Encrypt text"""
return self.fernet.encrypt(text.encode()).decode()
def _decrypt(self, encrypted_text):
"""Decrypt text"""
return self.fernet.decrypt(encrypted_text.encode()).decode()
def add_password(self, website, username, email, password, notes=""):
"""Add password entry"""
encrypted_username = self._encrypt(username)
encrypted_email = self._encrypt(email)
encrypted_password = self._encrypt(password)
encrypted_notes = self._encrypt(notes) if notes else ""
self.cursor.execute(
"INSERT INTO passwords (website, username, email, password, notes) VALUES (?, ?, ?, ?, ?)",
(website, encrypted_username, encrypted_email, encrypted_password, encrypted_notes)
)
self.conn.commit()
return True
def get_all_passwords(self):
"""Get all password entries"""
self.cursor.execute("SELECT id, website, username, email, password, notes FROM passwords")
entries = self.cursor.fetchall()
decrypted_entries = []
for entry in entries:
id_, website, encrypted_username, encrypted_email, encrypted_password, encrypted_notes = entry
decrypted_entries.append((
id_,
website,
self._decrypt(encrypted_username),
self._decrypt(encrypted_email),
self._decrypt(encrypted_password),
self._decrypt(encrypted_notes) if encrypted_notes else ""
))
return decrypted_entries
def search_passwords(self, keyword):
"""Search password entries"""
self.cursor.execute(
"SELECT id, website, username, email, password, notes FROM passwords WHERE website LIKE ? OR username LIKE ? OR email LIKE ?",
(f"%{keyword}%", f"%{keyword}%", f"%{keyword}%")
)
entries = self.cursor.fetchall()
decrypted_entries = []
for entry in entries:
id_, website, encrypted_username, encrypted_email, encrypted_password, encrypted_notes = entry
decrypted_entries.append((
id_,
website,
self._decrypt(encrypted_username),
self._decrypt(encrypted_email),
self._decrypt(encrypted_password),
self._decrypt(encrypted_notes) if encrypted_notes else ""
))
return decrypted_entries
def update_password(self, entry_id, website=None, username=None, email=None, password=None, notes=None):
"""Update password entry"""
if website is not None:
self.cursor.execute("UPDATE passwords SET website = ? WHERE id = ?", (website, entry_id))
if username is not None:
encrypted_username = self._encrypt(username)
self.cursor.execute("UPDATE passwords SET username = ? WHERE id = ?", (encrypted_username, entry_id))
if email is not None:
encrypted_email = self._encrypt(email)
self.cursor.execute("UPDATE passwords SET email = ? WHERE id = ?", (encrypted_email, entry_id))
if password is not None:
encrypted_password = self._encrypt(password)
self.cursor.execute("UPDATE passwords SET password = ? WHERE id = ?", (encrypted_password, entry_id))
if notes is not None:
encrypted_notes = self._encrypt(notes)
self.cursor.execute("UPDATE passwords SET notes = ? WHERE id = ?", (encrypted_notes, entry_id))
self.conn.commit()
return True
def delete_password(self, entry_id):
"""Delete password entry"""
self.cursor.execute("DELETE FROM passwords WHERE id = ?", (entry_id,))
self.conn.commit()
return True
def generate_password(self, length=16):
"""Generate random password"""
characters = string.ascii_letters + string.digits + "!@#$%^&*()"
return ''.join(random.choice(characters) for _ in range(length))
class PasswordManagerApp:
def __init__(self, root):
# Check if another instance is already running
if self.is_already_running():
messagebox.showerror(" 错误", "程序已经在运行中")
sys.exit(1)
self.root = root
self.root.title(" 密码管理器")
self.root.geometry("900x600")
# Track if instructions window is open
self.instructions_window_open = False
self.instructions_window = None
# Set program icon (cross-platform)
self.set_window_icon()
# Hide main window initially
self.root.withdraw()
self.pm = PasswordManager()
self.check_master_password()
def is_already_running(self):
"""Check if another instance of this program is already running"""
try:
# Try to create a socket (port will be occupied if already running)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 65432))
return False
except socket.error:
return True
def set_window_icon(self):
"""Set window icon, compatible with Windows/Linux/Mac"""
try:
if sys.platform.startswith('win'):
self.root.iconbitmap(default=self.resource_path("icon.ico"))
else:
img = tk.PhotoImage(file=self.resource_path("icon.png"))
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
except Exception as e:
print(f"图标加载失败: {e}")
def resource_path(self, relative_path):
"""Get absolute path to resource, works for dev and for PyInstaller"""
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def check_master_password(self):
"""Check master password status"""
self.pm.cursor.execute("SELECT * FROM master_password")
if not self.pm.cursor.fetchone():
self.show_set_master_password() # First time use
else:
self.show_login() # Already has password
def show_set_master_password(self):
"""Show set master password window"""
self.set_pw_window = tk.Toplevel(self.root)
self.set_pw_window.title(" 设置主密码")
self.set_pw_window.geometry("400x250")
self.set_pw_window.attributes('-topmost', True)
# Disable window close button
self.set_pw_window.protocol("WM_DELETE_WINDOW", lambda: None)
self.set_pw_window.grab_set()
tk.Label(self.set_pw_window, text="首次使用,请设置主密码", font=('Arial', 12)).pack(pady=10)
frame = tk.Frame(self.set_pw_window)
frame.pack(pady=10)
tk.Label(frame, text="主密码:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.master_pw_entry = tk.Entry(frame, show="*")
self.master_pw_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(frame, text="确认密码:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.confirm_pw_entry = tk.Entry(frame, show="*")
self.confirm_pw_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Button(self.set_pw_window, text="设置密码", command=self.set_master_password).pack(pady=20)
def set_master_password(self):
"""Set master password"""
pw = self.master_pw_entry.get()
confirm = self.confirm_pw_entry.get()
if not pw or not confirm:
messagebox.showerror(" 错误", "密码不能为空")
return
if pw != confirm:
messagebox.showerror(" 错误", "两次输入密码不一致")
return
if len(pw) < 8:
messagebox.showerror(" 错误", "密码长度至少8位")
return
self.pm.set_master_password(pw)
self.set_pw_window.destroy()
self.show_main_interface()
def show_login(self):
"""Show login window"""
self.login_window = tk.Toplevel(self.root)
self.login_window.title(" 验证主密码")
self.login_window.geometry("400x200")
self.login_window.attributes('-topmost', True)
self.login_window.grab_set()
self.login_window.protocol("WM_DELETE_WINDOW", lambda: None)
tk.Label(self.login_window, text="请输入主密码", font=('Arial', 12)).pack(pady=10)
self.login_pw_entry = tk.Entry(self.login_window, show="*")
self.login_pw_entry.pack(pady=10)
tk.Button(self.login_window, text="登录", command=self.verify_login).pack(pady=10)
# Bind Enter key
self.login_pw_entry.bind('<Return>', lambda e: self.verify_login())
def verify_login(self):
"""Verify password"""
pw = self.login_pw_entry.get()
if not pw:
messagebox.showerror(" 错误", "请输入密码")
return
if self.pm.verify_master_password(pw):
self.login_window.destroy()
self.show_main_interface()
else:
messagebox.showerror(" 错误", "密码错误")
self.login_pw_entry.delete(0, tk.END)
def show_main_interface(self):
"""Show main interface"""
self.root.deiconify() # Show main window
# Top button area
button_frame = tk.Frame(self.root)
button_frame.pack(fill=tk.X, padx=10, pady=10)
tk.Button(button_frame, text="添加密码", command=self.show_add_password).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="刷新列表", command=self.refresh_password_list).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="生成密码", command=self.show_generate_password).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="修改主密码", command=self.show_change_master_password).pack(side=tk.LEFT, padx=5)
# Search area
search_frame = tk.Frame(self.root)
search_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
self.search_entry = tk.Entry(search_frame)
self.search_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
tk.Button(search_frame, text="搜索", command=self.search_passwords).pack(side=tk.LEFT, padx=5)
# Password list
list_frame = tk.Frame(self.root)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.tree = ttk.Treeview(list_frame, columns=("id", "website", "username", "email"), show="headings")
self.tree.heading("id", text="ID")
self.tree.heading("website", text="网站")
self.tree.heading("username", text="用户名")
self.tree.heading("email", text="邮箱")
self.tree.column("id", width=50, anchor=tk.CENTER)
self.tree.column("website", width=150)
self.tree.column("username", width=150)
self.tree.column("email", width=200)
vsb = ttk.Scrollbar(list_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(list_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky=tk.NSEW)
vsb.grid(row=0, column=1, sticky=tk.NS)
hsb.grid(row=1, column=0, sticky=tk.EW)
list_frame.grid_rowconfigure(0, weight=1)
list_frame.grid_columnconfigure(0, weight=1)
self.tree.bind("<Double-1>", self.on_item_double_click)
# Bottom buttons - modified version
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(fill=tk.X, padx=10, pady=10)
# Create a container frame for centering
button_container = tk.Frame(bottom_frame)
button_container.pack(expand=True)
# Set button font style
button_font = ('Arial', 12, 'bold') # Larger and bold font
# Instruction button
tk.Button(button_container, text="操作说明",
command=self.show_instructions,
font=button_font).pack(side=tk.LEFT, padx=20)
# Copyright label
tk.Label(button_container, text="© 2025 科技之星制作免费密码管理器 v2.7",
font=('Arial', 10)).pack(side=tk.LEFT, padx=20)
# Exit button
tk.Button(button_container, text="退出",
command=self.root.quit,
font=button_font).pack(side=tk.LEFT, padx=20)
# Load password list
self.refresh_password_list()
def show_instructions(self):
"""Show instructions window (prevent multiple windows)"""
if self.instructions_window_open:
if self.instructions_window.winfo_exists():
self.instructions_window.lift()
self.instructions_window.focus_force()
return
self.instructions_window_open = True
instructions = """
===== 密码管理器使用说明 =====
1. 添加密码:
- 点击"添加密码"按钮
- 填写网站、用户名、邮箱和密码
- 点击"保存"按钮
2. 查看密码:
- 双击列表中的条目查看详情
- 点击"显示"按钮查看密码
3. 编辑密码:
- 在详情窗口中点击"编辑"按钮
- 修改需要更新的信息
- 点击"保存"按钮
4. 删除密码:
- 在详情窗口中点击"删除"按钮
- 确认删除操作
5. 搜索密码:
- 在搜索框中输入关键词
- 点击"搜索"按钮或按回车键
6. 生成密码:
- 点击"生成密码"按钮
- 设置密码长度
- 点击"生成"按钮
- 可以复制生成的密码
7. 修改主密码:
- 点击"修改主密码"按钮
- 输入当前密码和新密码
- 点击"修改"按钮
注意:
- 首次使用需要设置主密码
- 主密码用于保护所有存储的密码
- 请牢记您的主密码,无法找回
"""
# Create instructions window
self.instructions_window = tk.Toplevel(self.root)
self.instructions_window.title(" 操作说明")
self.instructions_window.geometry("600x400")
# Add close handler
def on_close():
self.instructions_window_open = False
self.instructions_window.destroy()
self.instructions_window.protocol("WM_DELETE_WINDOW", on_close)
# Add scrolled text widget
text_widget = scrolledtext.ScrolledText(self.instructions_window, wrap=tk.WORD, width=70, height=25)
text_widget.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
text_widget.insert(tk.END, instructions)
text_widget.config(state=tk.DISABLED)
# Add close button
close_button = tk.Button(self.instructions_window, text="关闭", command=on_close)
close_button.pack(pady=10)
def refresh_password_list(self):
"""Refresh password list"""
for item in self.tree.get_children():
self.tree.delete(item)
passwords = self.pm.get_all_passwords()
for pwd in passwords:
self.tree.insert("", tk.END, values=(pwd[0], pwd[1], pwd[2], pwd[3]))
def search_passwords(self):
"""Search passwords"""
keyword = self.search_entry.get()
for item in self.tree.get_children():
self.tree.delete(item)
passwords = self.pm.search_passwords(keyword)
for pwd in passwords:
self.tree.insert("", tk.END, values=(pwd[0], pwd[1], pwd[2], pwd[3]))
def show_add_password(self):
"""Show add password window"""
self.add_window = tk.Toplevel(self.root)
self.add_window.title(" 添加密码")
self.add_window.geometry("400x350")
tk.Label(self.add_window, text="添加新密码", font=('Arial', 12)).pack(pady=10)
frame = tk.Frame(self.add_window)
frame.pack(pady=10)
tk.Label(frame, text="网站/应用:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.website_entry = tk.Entry(frame)
self.website_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(frame, text="用户名:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.username_entry = tk.Entry(frame)
self.username_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Label(frame, text="邮箱:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.email_entry = tk.Entry(frame)
self.email_entry.grid(row=2, column=1, padx=5, pady=5)
tk.Label(frame, text="密码:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.password_entry = tk.Entry(frame, show="*")
self.password_entry.grid(row=3, column=1, padx=5, pady=5)
tk.Button(frame, text="生成", command=self.insert_generated_password).grid(row=3, column=2, padx=5)
tk.Label(frame, text="备注:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.notes_entry = tk.Entry(frame)
self.notes_entry.grid(row=4, column=1, padx=5, pady=5)
button_frame = tk.Frame(self.add_window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="保存", command=self.save_password).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", command=self.add_window.destroy).pack(side=tk.LEFT, padx=10)
def insert_generated_password(self):
"""Insert generated password"""
password = self.pm.generate_password()
self.password_entry.delete(0, tk.END)
self.password_entry.insert(0, password)
def save_password(self):
"""Save password"""
website = self.website_entry.get()
username = self.username_entry.get()
email = self.email_entry.get()
password = self.password_entry.get()
notes = self.notes_entry.get()
if not website:
messagebox.showerror(" 错误", "网站不能为空")
return
if not username and not email:
messagebox.showerror(" 错误", "用户名或邮箱至少填写一项")
return
if not password:
messagebox.showerror(" 错误", "密码不能为空")
return
if self.pm.add_password(website, username, email, password, notes):
messagebox.showinfo(" 成功", "密码添加成功")
self.add_window.destroy()
self.refresh_password_list()
else:
messagebox.showerror(" 错误", "添加密码失败")
def on_item_double_click(self, event):
"""Double click password entry"""
item = self.tree.selection()[0]
item_data = self.tree.item(item, "values")
entry_id = item_data[0]
self.show_password_details(entry_id)
def show_password_details(self, entry_id):
"""Show password details"""
passwords = self.pm.get_all_passwords()
password = None
for pwd in passwords:
if str(pwd[0]) == str(entry_id):
password = pwd
break
if not password:
messagebox.showerror(" 错误", "未找到密码条目")
return
self.detail_window = tk.Toplevel(self.root)
self.detail_window.title(" 密码详情")
self.detail_window.geometry("500x400")
tk.Label(self.detail_window, text="密码详情", font=('Arial', 12)).pack(pady=10)
detail_frame = tk.Frame(self.detail_window)
detail_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
tk.Label(detail_frame, text="网站:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
tk.Label(detail_frame, text=password[1]).grid(row=0, column=1, padx=5, pady=5, sticky='w')
tk.Label(detail_frame, text="用户名:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
tk.Label(detail_frame, text=password[2]).grid(row=1, column=1, padx=5, pady=5, sticky='w')
tk.Label(detail_frame, text="邮箱:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
tk.Label(detail_frame, text=password[3]).grid(row=2, column=1, padx=5, pady=5, sticky='w')
tk.Label(detail_frame, text="密码:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.password_var = tk.StringVar(value="********")
tk.Label(detail_frame, textvariable=self.password_var).grid(row=3, column=1, padx=5, pady=5, sticky='w')
tk.Button(detail_frame, text="显示", command=lambda: self.toggle_password(password[4])).grid(row=3, column=2, padx=5)
tk.Label(detail_frame, text="备注:").grid(row=4, column=0, padx=5, pady=5, sticky='ne')
notes_text = scrolledtext.ScrolledText(detail_frame, wrap=tk.WORD, width=40, height=10)
notes_text.grid(row=4, column=1, columnspan=2, padx=5, pady=5, sticky='w')
notes_text.insert(tk.END, password[5])
notes_text.config(state=tk.DISABLED)
button_frame = tk.Frame(self.detail_window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="编辑", command=lambda: self.edit_password(password)).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="删除", command=lambda: self.delete_password(password[0])).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="关闭", command=self.detail_window.destroy).pack(side=tk.LEFT, padx=10)
def toggle_password(self, password):
"""Toggle password visibility"""
if self.password_var.get() == "********":
self.password_var.set(password)
else:
self.password_var.set("********")
def edit_password(self, password):
"""Edit password"""
self.detail_window.destroy()
self.edit_window = tk.Toplevel(self.root)
self.edit_window.title(" 编辑密码")
self.edit_window.geometry("400x350")
tk.Label(self.edit_window, text="编辑密码", font=('Arial', 12)).pack(pady=10)
frame = tk.FFrame(self.edit_window)
frame.pack(pady=10)
tk.Label(frame, text="网站/应用:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.edit_website_entry = tk.Entry(frame)
self.edit_website_entry.grid(row=0, column=1, padx=5, pady=5)
self.edit_website_entry.insert(0, password[1])
tk.Label(frame, text="用户名:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.edit_username_entry = tk.Entry(frame)
self.edit_username_entry.grid(row=1, column=1, padx=5, pady=5)
self.edit_username_entry.insert(0, password[2])
tk.Label(frame, text="邮箱:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.edit_email_entry = tk.Entry(frame)
self.edit_email_entry.grid(row=2, column=1, padx=5, pady=5)
self.edit_email_entry.insert(0, password[3])
tk.Label(frame, text="密码:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.edit_password_entry = tk.Entry(frame, show="*")
self.edit_password_entry.grid(row=3, column=1, padx=5, pady=5)
self.edit_password_entry.insert(0, password[4])
tk.Button(frame, text="生成", command=self.insert_generated_password_edit).grid(row=3, column=2, padx=5)
tk.Label(frame, text="备注:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.edit_notes_entry = tk.Entry(frame)
self.edit_notes_entry.grid(row=4, column=1, padx=5, pady=5)
self.edit_notes_entry.insert(0, password[5])
button_frame = tk.Frame(self.edit_window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="保存", command=lambda: self.save_edited_password(password[0])).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", command=self.edit_window.destroy).pack(side=tk.LEFT, padx=10)
def insert_generated_password_edit(self):
"""Insert generated password in edit window"""
password = self.pm.generate_password()
self.edit_password_entry.delete(0, tk.END)
self.edit_password_entry.insert(0, password)
def save_edited_password(self, entry_id):
"""Save edited password"""
website = self.edit_website_entry.get()
username = self.edit_username_entry.get()
email = self.edit_email_entry.get()
password = self.edit_password_entry.get()
notes = self.edit_notes_entry.get()
if not website:
messagebox.showerror(" 错误", "网站不能为空")
return
if not username and not email:
messagebox.showerror(" 错误", "用户名或邮箱至少填写一项")
return
if not password:
messagebox.showerror(" 错误", "密码不能为空")
return
if self.pm.update_password(
entry_id,
website=website,
username=username,
email=email,
password=password,
notes=notes
):
messagebox.showinfo(" 成功", "密码更新成功")
self.edit_window.destroy()
self.refresh_password_list()
else:
messagebox.showerror(" 错误", "更新密码失败")
def delete_password(self, entry_id):
"""Delete password"""
if messagebox.askyesno(" 确认", "确定要删除此密码条目吗?"):
if self.pm.delete_password(entry_id):
messagebox.showinfo(" 成功", "密码删除成功")
self.detail_window.destroy()
self.refresh_password_list()
else:
messagebox.showerror(" 错误", "删除密码失败")
def show_generate_password(self):
"""Show generate password window"""
gen_window = tk.Toplevel(self.root)
gen_window.title(" 生成密码")
gen_window.geometry("300x200")
tk.Label(gen_window, text="生成随机密码", font=('Arial', 12)).pack(pady=10)
frame = tk.Frame(gen_window)
frame.pack(pady=10)
tk.Label(frame, text="密码长度:").grid(row=0, column=0, padx=5, pady=5)
self.length_var = tk.IntVar(value=16)
tk.Entry(frame, textvariable=self.length_var, width=5).grid(row=0, column=1, padx=5, pady=5)
self.generated_pw_var = tk.StringVar()
tk.Entry(frame, textvariable=self.generated_pw_var, width=20).grid(row=1, column=0, columnspan=2, padx=5, pady=5)
tk.Button(frame, text="生成", command=self.generate_and_show_password).grid(row=2, column=0, columnspan=2, pady=10)
button_frame = tk.Frame(gen_window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="复制", command=self.copy_generated_password).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="关闭", command=gen_window.destroy).pack(side=tk.LEFT, padx=10)
def generate_and_show_password(self):
"""Generate and show password"""
length = self.length_var.get()
if length < 8 or length > 32:
messagebox.showerror(" 错误", "密码长度应在8-32之间")
return
password = self.pm.generate_password(length)
self.generated_pw_var.set(password)
def copy_generated_password(self):
"""Copy generated password"""
password = self.generated_pw_var.get()
if password:
self.root.clipboard_clear()
self.root.clipboard_append(password)
messagebox.showinfo(" 成功", "密码已复制到剪贴板")
def show_change_master_password(self):
"""Show change master password window"""
change_window = tk.Toplevel(self.root)
change_window.title(" 修改主密码")
change_window.geometry("400x300")
tk.Label(change_window, text="修改主密码", font=('Arial', 12)).pack(pady=10)
frame = tk.Frame(change_window)
frame.pack(pady=10)
tk.Label(frame, text="当前密码:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.current_pw_entry = tk.Entry(frame, show="*")
self.current_pw_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(frame, text="新密码:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.new_pw_entry = tk.Entry(frame, show="*")
self.new_pw_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Label(frame, text="确认新密码:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.confirm_new_pw_entry = tk.Entry(frame, show="*")
self.confirm_new_pw_entry.grid(row=2, column=1, padx=5, pady=5)
button_frame = tk.Frame(change_window)
button_frame.pack(pady=10)
tk.Button(button_frame, text="修改", command=self.change_master_password).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", command=change_window.destroy).pack(side=tk.LEFT, padx=10)
def change_master_password(self):
"""Change master password"""
current_pw = self.current_pw_entry.get()
new_pw = self.new_pw_entry.get()
confirm_new_pw = self.confirm_new_pw_entry.get()
if not current_pw or not new_pw or not confirm_new_pw:
messagebox.showerror(" 错误", "所有字段必须填写")
return
if not self.pm.verify_master_password(current_pw):
messagebox.showerror(" 错误", "当前密码错误")
return
if new_pw != confirm_new_pw:
messagebox.showerror(" 错误", "新密码不匹配")
return
if len(new_pw) < 8:
messagebox.showerror(" 错误", "密码长度至少8位")
return
self.pm.set_master_password(new_pw)
messagebox.showinfo(" 成功", "主密码修改成功")
self.current_pw_entry.delete(0, tk.END)
self.new_pw_entry.delete(0, tk.END)
self.confirm_new_pw_entry.delete(0, tk.END)
change_window = self.current_pw_entry.winfo_toplevel()
change_window.destroy()
def main():
root = tk.Tk()
app = PasswordManagerApp(root)
root.mainloop()
if __name__ == "__main__":
main()
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
密码数据库查看器
详细介绍
1. 软件概述
密码数据库查看器是一款专为管理加密密码数据库设计的桌面应用程序,它能够安全地解密、查看和导出存储在SQLite数据库中的密码记录。该工具特别适用于查看由密码管理器生成的加密数据库,提供了直观的用户界面和强大的搜索功能。
2. 核心功能
2.1 数据库加载与解密
- 文件选择:支持从任意磁盘位置选择数据库文件(passwords.db)和加密密钥文件(secret.key)
- 自动解密:使用Fernet对称加密算法自动解密数据库内容
- 智能识别:在"重新加载"功能中自动识别文件夹内的.db和.key文件
2.2 数据展示
- 表格视图:以表格形式展示所有密码记录,包括ID、网站、用户名和邮箱
- 详情查看:双击记录可查看完整信息,包括密码和备注
- 密码保护:密码默认显示为"",需点击"显示"按钮才能查看真实密码
2.3 搜索与筛选 - 实时搜索:支持按网站、用户名或邮箱进行关键词搜索
- 结果统计:显示当前匹配的记录数量
2.4 数据导出 - 文本导出:可将所有解密数据导出为格式化的文本文件
- 自定义路径:允许用户选择导出文件的位置和名称
3. 技术实现
3.1 加密技术
- 使用Python的
cryptography
库实现Fernet加密 - 基于256位AES算法的对称加密
- 密钥存储在独立的.key文件中
3.2 数据库操作 - 使用SQLite3作为数据库后端
- 数据库表结构包含:
- id: 唯一标识符
- website: 网站名称
- username: 用户名(加密存储)
- email: 邮箱(加密存储)
- password: 密码(加密存储)
- notes: 备注(加密存储)
3.3 用户界面
- 基于Tkinter的GUI框架
- 使用ttk控件实现现代化外观
- 响应式布局适应不同窗口大小
- 状态栏实时显示操作状态
4. 使用场景
4.1 密码恢复
- 当忘记某个网站的密码时,可快速查找
- 支持通过网站名称、用户名或邮箱进行搜索
4.2 密码备份
- 定期导出密码数据作为备份
- 明文导出可用于迁移到其他密码管理器
4.3 多设备同步 - 在不同设备间共享密码数据库
- 只需复制.db和.key文件即可实现数据迁移
- 安全注意事项
- 密钥保护:secret.key文件等同于密码,需妥善保管
- 导出风险:导出的文本文件包含明文密码,应加密存储
- 使用环境:建议在可信设备上使用本工具
- 文件权限:确保数据库和密钥文件有适当的访问权限
- 扩展功能建议
- 密码生成器:集成随机密码生成功能
- 自动锁定:闲置一段时间后自动锁定数据库
- 云备份:支持加密备份到云存储
- 多数据库管理:同时管理多个密码数据库
- 密码强度分析:评估现有密码的安全性
- 编译与分发
可将Python脚本编译为独立可执行文件:pyinstaller --onefile --windowed password_viewer.py
建议分发时包含:
- 主程序(EXE或Python脚本)
- 使用说明文档
- 示例数据库和密钥(仅用于演示)
- 系统要求
- 操作系统:Windows/Linux/macOS
- Python版本:3.6或更高
- 依赖库:
- tkinter
- cryptography
- sqlite3
这个密码数据库查看器为个人密码管理提供了安全便捷的解决方案,特别适合需要查看和管理加密密码数据库的用户。通过直观的界面和强大的功能,用户可以轻松访问自己的密码信息,同时确保数据的安全性。密码数据库查看器已开源
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import sqlite3
from cryptography.fernet import Fernet
import os
class PasswordManagerViewer:
def __init__(self, root):
self.root = root
self.root.title(" 密码数据库查看器")
self.root.geometry("900x600")
# 初始化时不自动加载数据库
self.db_path = None
self.key_path = None
# 初始化UI
self.setup_ui()
# 状态变量
self.fernet = None
self.conn = None
self.cursor = None
self.decrypted_data = []
def setup_ui(self):
"""设置用户界面"""
# 顶部控制区域
control_frame = tk.Frame(self.root)
control_frame.pack(fill=tk.X, padx=10, pady=10)
# 添加"选择数据库"按钮
tk.Button(control_frame, text="选择数据库", command=self.select_database).pack(side=tk.LEFT)
# 修改"重新加载"按钮功能
tk.Button(control_frame, text="重新加载", command=self.reload_database).pack(side=tk.LEFT, padx=10)
tk.Button(control_frame, text="导出数据", command=self.export_data).pack(side=tk.LEFT)
# 搜索框
search_frame = tk.Frame(control_frame)
search_frame.pack(side=tk.RIGHT)
tk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
self.search_entry = tk.Entry(search_frame, width=30)
self.search_entry.pack(side=tk.LEFT, padx=5)
self.search_entry.bind('<Return>', lambda e: self.search_data())
tk.Button(search_frame, text="搜索", command=self.search_data).pack(side=tk.LEFT)
# 主显示区域
self.tree_frame = tk.Frame(self.root)
self.tree_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 创建空的树状视图
self.create_empty_treeview()
# 底部状态栏
self.status_var = tk.StringVar()
self.status_var.set(" 请选择数据库文件")
tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X)
# 详情窗口变量
self.detail_window = None
def create_empty_treeview(self):
"""创建空的树状视图"""
for widget in self.tree_frame.winfo_children():
widget.destroy()
self.tree = ttk.Treeview(self.tree_frame, columns=("id", "website", "username", "email"), show="headings")
# 设置列
self.tree.heading("id", text="ID")
self.tree.heading("website", text="网站")
self.tree.heading("username", text="用户名")
self.tree.heading("email", text="邮箱")
self.tree.column("id", width=50, anchor=tk.CENTER)
self.tree.column("website", width=150)
self.tree.column("username", width=150)
self.tree.column("email", width=200)
# 添加滚动条
vsb = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(self.tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky=tk.NSEW)
vsb.grid(row=0, column=1, sticky=tk.NS)
hsb.grid(row=1, column=0, sticky=tk.EW)
# 配置网格布局
self.tree_frame.grid_rowconfigure(0, weight=1)
self.tree_frame.grid_columnconfigure(0, weight=1)
def select_database(self):
"""选择数据库文件和密钥文件"""
# 选择数据库文件
db_path = filedialog.askopenfilename(
title="选择密码数据库文件",
filetypes=[("SQLite 数据库", "*.db"), ("所有文件", "*.*")]
)
if not db_path:
return
# 选择密钥文件
key_path = filedialog.askopenfilename(
title="选择加密密钥文件",
filetypes=[("密钥文件", "*.key"), ("所有文件", "*.*")]
)
if not key_path:
return
self.db_path = db_path
self.key_path = key_path
self.status_var.set(f" 已选择数据库: {os.path.basename(db_path)}")
# 自动加载数据库
self.load_database()
def reload_database(self):
"""重新加载数据库,允许选择新文件夹"""
# 弹出文件夹选择对话框
folder_path = filedialog.askdirectory(
title="选择包含数据库文件的文件夹"
)
if not folder_path:
return
# 尝试在选定文件夹中查找数据库文件和密钥文件
db_path = None
key_path = None
for file in os.listdir(folder_path):
file_path = os.path.join(folder_path, file)
if file.lower().endswith('.db'):
db_path = file_path
elif file.lower().endswith('.key'):
key_path = file_path
if not db_path:
messagebox.showerror(" 错误", "在选定文件夹中未找到数据库文件(.db)")
return
if not key_path:
messagebox.showerror(" 错误", "在选定文件夹中未找到密钥文件(.key)")
return
self.db_path = db_path
self.key_path = key_path
self.status_var.set(f" 已从 {os.path.basename(folder_path)} 加载数据库")
# 加载数据库
self.load_database()
def load_database(self):
"""加载并显示数据库内容"""
if not self.db_path or not self.key_path:
messagebox.showwarning(" 警告", "请先选择数据库文件和密钥文件")
return
try:
# 检查文件是否存在
if not os.path.exists(self.db_path):
self.status_var.set(f" 错误: 数据库文件 {self.db_path} 不存在")
return
if not os.path.exists(self.key_path):
self.status_var.set(f" 错误: 密钥文件 {self.key_path} 不存在")
return
# 加载加密密钥
with open(self.key_path, "rb") as f:
key = f.read()
self.fernet = Fernet(key)
# 连接数据库
self.conn = sqlite3.connect(self.db_path)
self.cursor = self.conn.cursor()
# 获取所有密码记录
self.cursor.execute("SELECT id, website, username, email, password, notes FROM passwords")
records = self.cursor.fetchall()
# 解密数据
self.decrypted_data = []
for record in records:
try:
decrypted_record = (
record[0], # ID
record[1], # 网站
self._decrypt(record[2]), # 用户名
self._decrypt(record[3]), # 邮箱
self._decrypt(record[4]), # 密码
self._decrypt(record[5]) if record[5] else "" # 备注
)
self.decrypted_data.append(decrypted_record)
except Exception as e:
print(f"解密记录 {record[0]} 时出错: {e}")
# 显示数据
self.display_data(self.decrypted_data)
self.status_var.set(f" 成功加载 {len(self.decrypted_data)} 条记录 (来自: {os.path.basename(self.db_path)})")
except Exception as e:
self.status_var.set(f" 错误: {str(e)}")
messagebox.showerror(" 错误", f"加载数据库时出错:\n{str(e)}")
def _decrypt(self, encrypted_text):
"""解密文本"""
if not encrypted_text:
return ""
return self.fernet.decrypt(encrypted_text.encode()).decode()
def display_data(self, data):
"""在表格中显示数据"""
# 清除旧的树状视图
for widget in self.tree_frame.winfo_children():
widget.destroy()
# 创建新的树状视图
self.tree = ttk.Treeview(self.tree_frame, columns=("id", "website", "username", "email"), show="headings")
# 设置列
self.tree.heading("id", text="ID")
self.tree.heading("website", text="网站")
self.tree.heading("username", text="用户名")
self.tree.heading("email", text="邮箱")
self.tree.column("id", width=50, anchor=tk.CENTER)
self.tree.column("website", width=150)
self.tree.column("username", width=150)
self.tree.column("email", width=200)
# 添加滚动条
vsb = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(self.tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky=tk.NSEW)
vsb.grid(row=0, column=1, sticky=tk.NS)
hsb.grid(row=1, column=0, sticky=tk.EW)
# 配置网格布局
self.tree_frame.grid_rowconfigure(0, weight=1)
self.tree_frame.grid_columnconfigure(0, weight=1)
# 添加数据
for record in data:
self.tree.insert("", tk.END, values=(record[0], record[1], record[2], record[3]))
# 绑定双击事件查看详情
self.tree.bind("<Double-1>", self.show_detail)
def search_data(self):
"""搜索数据"""
if not self.decrypted_data:
return
keyword = self.search_entry.get().lower()
if not keyword:
self.display_data(self.decrypted_data)
return
filtered = [record for record in self.decrypted_data
if (keyword in record[1].lower() or
keyword in record[2].lower() or
keyword in record[3].lower())]
self.display_data(filtered)
self.status_var.set(f" 找到 {len(filtered)} 条匹配记录")
def show_detail(self, event):
"""显示选中记录的详细信息"""
if not self.decrypted_data:
return
item = self.tree.selection()[0]
item_data = self.tree.item(item, "values")
record_id = item_data[0]
# 查找完整记录
record = None
for r in self.decrypted_data:
if str(r[0]) == str(record_id):
record = r
break
if not record:
return
# 如果详情窗口已存在,先关闭
if self.detail_window and self.detail_window.winfo_exists():
self.detail_window.destroy()
# 创建详情窗口
self.detail_window = tk.Toplevel(self.root)
self.detail_window.title(f" 密码详情 - {record[1]}")
self.detail_window.geometry("500x400")
# 添加控件
tk.Label(self.detail_window, text=f"{record[1]} 的详细信息", font=('Arial', 12)).pack(pady=10)
detail_frame = tk.Frame(self.detail_window)
detail_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 网站
tk.Label(detail_frame, text="网站:").grid(row=0, column=0, sticky='e', padx=5, pady=5)
tk.Label(detail_frame, text=record[1]).grid(row=0, column=1, sticky='w', padx=5, pady=5)
# 用户名
tk.Label(detail_frame, text="用户名:").grid(row=1, column=0, sticky='e', padx=5, pady=5)
tk.Label(detail_frame, text=record[2]).grid(row=1, column=1, sticky='w', padx=5, pady=5)
# 邮箱
tk.Label(detail_frame, text="邮箱:").grid(row=2, column=0, sticky='e', padx=5, pady=5)
tk.Label(detail_frame, text=record[3]).grid(row=2, column=1, sticky='w', padx=5, pady=5)
# 密码
tk.Label(detail_frame, text="密码:").grid(row=3, column=0, sticky='e', padx=5, pady=5)
self.password_var = tk.StringVar(value="********")
tk.Label(detail_frame, textvariable=self.password_var).grid(row=3, column=1, sticky='w', padx=5, pady=5)
tk.Button(detail_frame, text="显示", command=lambda: self.toggle_password(record[4])).grid(row=3, column=2, padx=5)
# 备注
tk.Label(detail_frame, text="备注:").grid(row=4, column=0, sticky='ne', padx=5, pady=5)
notes_text = scrolledtext.ScrolledText(detail_frame, wrap=tk.WORD, width=40, height=10)
notes_text.grid(row=4, column=1, columnspan=2, sticky='w', padx=5, pady=5)
notes_text.insert(tk.END, record[5])
notes_text.config(state=tk.DISABLED)
# 关闭按钮
tk.Button(self.detail_window, text="关闭", command=self.detail_window.destroy).pack(pady=10)
def toggle_password(self, password):
"""切换密码显示/隐藏"""
if self.password_var.get() == "********":
self.password_var.set(password)
else:
self.password_var.set("********")
def export_data(self):
"""导出数据到文本文件"""
if not self.decrypted_data:
messagebox.showwarning(" 警告", "没有可导出的数据")
return
try:
file_path = filedialog.asksaveasfilename(
title="导出数据到文件",
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if not file_path:
return
with open(file_path, "w", encoding="utf-8") as f:
f.write(" 密码数据库导出\n")
f.write("="*50 + "\n\n")
for record in self.decrypted_data:
f.write(f"ID: {record[0]}\n")
f.write(f" 网站: {record[1]}\n")
f.write(f" 用户名: {record[2]}\n")
f.write(f" 邮箱: {record[3]}\n")
f.write(f" 密码: {record[4]}\n")
f.write(f" 备注: {record[5]}\n")
f.write("-"*50 + "\n")
self.status_var.set(f" 数据已导出到 {os.path.basename(file_path)}")
messagebox.showinfo(" 成功", f"数据已成功导出到:\n{file_path}")
except Exception as e:
messagebox.showerror(" 错误", f"导出数据时出错:\n{str(e)}")
def main():
root = tk.Tk()
app = PasswordManagerViewer(root)
root.mainloop()
if __name__ == "__main__":
main()
GRUD6 个月前
通告声明: 关于回帖问题 由于本站长要求,禁止刷1234等!存在恶意灌水回复,已开启自动审核制,自动封闭IP,禁止再次注册!请知晓!
有什么问题群内咨询 561116458
System7 个月前
网络技术QQ:561116458
科技之星①群:669812887
软件共享群:34008xxxx【因为是VIP软件不公开】
视频教程 短视频平台搜索:科技之星网络