20252322 吴坤昌 2025-2026-2 《Python程序设计》综合实践(实验4)报告
20252322 吴坤昌 2025-2026-2 《Python程序设计》综合实践(实验4)报告
课程:Python程序设计
班级:2523
姓名:吴坤昌
学号:20252322
实验教师:王志强
实验日期:2026年6月12日
必修/选修:公选课
本次代码上传到git仓库(密码管理测评系统):https://gitee.com/wu-kunchang/kkk.git
一、实验分析(项目意义)
1、项目背景:
如今日常需要注册登录的平台账号越来越多,很多人为了方便,习惯设置简单密码,或是直接把账号密码明文记录在文档里,很容易出现个人信息被盗的情况。市面上常见的密码管理软件大多把数据存在云端,一旦平台出现漏洞,存储的密码就有泄露隐患;同时不少工具还需要额外安装各类依赖包,新手使用起来比较麻烦。(这一段背景是我查询AI找方向时注意的)。基于以上现实问题,在借助大模型的基础上,我设计开发了这套本地运行、无需额外依赖的密码管理系统,方便普通人安全保管各类账号密码。
2、项目意义:
- 安全防护:数据全程本地存储,结合 PBKDF2 密钥派生、对称加密等技术保护敏感信息
- 实用价值:集成密码管理、密码生成、强度检测、动态口令、剪贴板安全管控等功能
二、实验设计
- 主流程图:
![image]()
技术使用:
- tkinter GUI:搭建可视化操作界面,实现窗口、按钮、表格、弹窗等交互组件
- SQLite 数据库:本地持久化存储账号、密码等数据,完成数据增删改查
- JSON 文件读写:保存主密码盐值、登录状态、锁定时长等系统配置
- PBKDF2 + 哈希算法:基于主密码派生加密密钥,提升密钥安全性(没懂)
- 随机数生成:生成随机盐值、高强度密码,规避弱密钥风险
- TOTP 动态口令:实现动态验证码生成,补充账号安全验证能力(没看懂)
- 异常捕获:拦截运行错误,保证程序稳定运行
三、关于如何运行和操作的说明
简要操作说明:
- 添加密码:点击主界面「添加」按钮,填写网站、用户名、密码等信息,保存后自动加入密码列表;
- 编辑 / 删除:选中列表条目,点击「编辑」「删除」;
- 密码生成:右侧「密码生成器」配置参数(可选数字,字母,特殊字符等等),点击生成并可一键复制;
- 强度检测:在密码测评框输入内容,自动评估强弱并给出建议;
- 复制密码:双击列表操作列,选择「复制密码」,剪贴板 30 秒后自动清空。(此外还有隐藏密码,显示密码的功能一并包括)
文件说明:
程序运行后在同级目录会生成 2 个本地文件
config.json:存储主密码、校验密文、登录锁定状态;
passwords.db:SQLite 数据库,加密存储所有账号密码数据。
四、核心模块分析:
- PBKDF2 密钥:
借助 Python 自带的哈希工具,从用户设置的主密码换算出专属加密密钥,不会直接用原始明文密码加密存储数据
点击查看代码
def derive_key(self, password: str, salt: bytes) -> bytes:
key = hashlib.pbkdf2_hmac(
'sha256', # 哈希算法
password.encode('utf-8'), # 主密码转字节流
salt, # 随机盐值
100000, # 迭代次数,提升破解难度
dklen=32 # 输出密钥长度32字节
)
return key
- 数据加密与解密(这里采用了异或加密 + Base64):
依靠生成好的密钥完成异或加密,在加密内容前添加随机字符干扰数据,再用 Base64 转换成文本格式
点击查看代码
def encrypt_data(self, plaintext: str) -> str:
if not self.key:
raise ValueError("未登录")
data = plaintext.encode('utf-8')
# 异或加密
encrypted = bytearray()
for i, byte in enumerate(data):
key_byte = self.key[i % len(self.key)]
encrypted.append(byte ^ key_byte)
# 拼接16位随机前缀,增加混淆
random_prefix = secrets.token_bytes(16)
encrypted_data = random_prefix + bytes(encrypted)
# Base64编码输出
return base64.b64encode(encrypted_data).decode('utf-8')
- SQLite 数据库初始化与数据操作:
利用自带的 sqlite3 创建本地数据库和存储密码的数据表,完成账号信息的添加、修改、删除与查询操作。
点击查看代码
def init_database(self):
"""初始化密码数据库,创建数据表"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
# 创建密码存储表
cursor.execute('''
CREATE TABLE IF NOT EXISTS passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
notes TEXT,
totp_secret TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
- 密码强度评估算法:
计算密码安全强度,结合字符类型、长度综合打分,划分弱 / 中 / 强 / 很强四个等级
点击查看代码
@staticmethod
def calculate_password_strength(password: str) -> Tuple[int, str, str]:
"""计算密码强度:分数0-100、强度等级、颜色标识"""
if not password:
return 0, "弱", "red"
# 统计字符集规模
pool_size = 0
if any(c.islower() for c in password):
pool_size += 26
if any(c.isupper() for c in password):
pool_size += 26
if any(c.isdigit() for c in password):
pool_size += 10
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
pool_size += 20
entropy = len(password) * math.log2(pool_size)
# 根据熵值划分强度等级
if entropy < 28:
score, level, color = 20, "弱", "red"
elif entropy < 36:
score, level, color = 40, "中", "orange"
elif entropy < 60:
score, level, color = 70, "强", "yellow"
else:
score, level, color = 100, "很强", "green"
# 长度加分
length_bonus = min(len(password) * 2, 20)
score = min(score + length_bonus, 100)
return score, level, color
- 登录锁定:
记录登录尝试次数,连续 3 次输错密码则锁定登录 5 分钟
点击查看代码
def _handle_login_failure(self) -> Tuple[bool, str]:
"""处理登录失败逻辑,累计错误次数并触发锁定"""
self.login_attempts += 1
self._update_login_attempts(self.login_attempts)
# 连续3次错误,锁定5分钟
if self.login_attempts >= 3:
locked_until = datetime.now() + timedelta(minutes=5)
self._lock_login(locked_until)
return False, "连续错误3次,登录已锁定5分钟"
return False, f"密码错误,还剩 {3 - self.login_attempts} 次机会"
def is_locked(self) -> bool:
"""检查当前是否处于登录锁定状态"""
if not os.path.exists(self.config_file):
return False
with open(self.config_file, 'r') as f:
config = json.load(f)
locked_until = config.get('locked_until')
if locked_until:
locked_until = datetime.fromisoformat(locked_until)
# 锁定超时则自动解锁、重置次数
if datetime.now() >= locked_until:
config['locked_until'] = None
config['login_attempts'] = 0
with open(self.config_file, 'w') as f:
json.dump(config, f)
return False
return True
return False
五、实验结果
-
登录页面:
![image]()
-
菜单页面(可滚动下滑,下文会体现密码强度测评功能):
![image]()
-
添加密码(可选择是否填写TOTP动态验证码,我这里选择填写,下面结果会显示运行时长):
![image]()
(含有显示/隐藏代码、复制代码功能):
![image]()
-
删除密码:
![image]()
![image]()
-
编辑密码功能:
![image]()
-
刷新功能:
![image]()
-
复制密码,右侧有提示剪切板:
![image]()
-
自动生成密码(可选择密码长度,含有数字、字符、字母等选项来生成代码,并且生成完自动复制代码在剪切板和密码强度评估输入框内):
![image]()
-
密码强度评估和建议功能(根据密码长度和字符类型评估):
![image]()
六、华为云服务器运行
运行结果如下:

七、课程总结与感想
课程知识回顾:
简单制作的思维导图如下:

以下是课程知识总结
第一层:基础语法与逻辑控制
初识Python:掌握Python解释型语言特性与开发环境配置。深刻体会其与编译型语言的差异,正如王老师讲的,C语言如“盖浇饭”需预先编译,Python如“蛋炒饭”即写即执行。
语法基础:掌握变量定义、四大基础数据类型及各类运算符的使用规则,严格遵守Python缩进规范与变量、函数命名准则,然后就是标注的符号
流程控制:分支与循环结构
第二层:数据结构与文本处理
序列:列表可增删改查,元组只读,字典存键值对,集合可以去除重复数据
字符串:常用 strip()、split()、replace();正则表达式有 re.search()、re.findall() re.findall()
第三层:程序设计与模块化
函数:掌握函数定义(用def定义)(和C语言类似)
面向对象:学习类与对象的关系,正如“类是菜谱,对象是根据菜谱炒出来的菜”,了解封装、继承与多态三要素。
模块:学习了模块与包的导入机制
第四层:异常处理与数据持久化
异常处理:学习try-except等四种异常处理方法
文件操作:运用with语句进行文件读写
第五层:综合应用与工程实践
网络爬虫:用requests爬取数据,需要规范运用爬虫,不然要进小黑屋
tkinter GUI:有一次实验课用到GUI做界面
课程感想
这学期,和有趣的王老师学习Python,我从零基础入门,学习到很多python的知识,学到了很多,只是遗憾课后没能多多练习,导致有些知识掌握的不是很好。王老师课堂氛围轻松活跃,授课风趣易懂,擅长用生活化的比喻拆解抽象的编程概念,让晦涩的知识点变得很好理解。
本学期我学习了序列、函数、异常处理、文件操作、网络爬虫、数据可视化等知识。王老师讲课很有趣,会带着我们一起敲代码,感受非常非常好!如果可以,希望以后还能碰到王老师教我!!!
七、意见与建议
感觉有时候讲的太快,王老师敲代码敲的太快有点没跟上(可能是我自己的原因(尴尬)),然后就是知识量有时候比较大,就是真正去敲代码不太会,练得少。总体讲得很好很有趣,点赞!!!
最后祝王老师身体健康,生活愉快!
附上完整代码:
点击查看代码
# -*- coding: utf-8 -*-
"""
本地化零信任密码管理与健康度评估系统
完全使用Python内置标准库,无需安装任何第三方库
"""
import sys
import os
import json
import sqlite3
import base64
import hashlib
import secrets
import string
import math
import hmac
import struct
import time
from datetime import datetime, timedelta
from typing import Optional, Tuple, List, Dict
# 使用Python内置的tkinter库(无需安装)
import tkinter as tk
from tkinter import ttk, messagebox
# ==================== 安全引擎类 ====================
class SecurityEngine:
"""安全引擎核心类(完全使用Python标准库)"""
def __init__(self, config_file: str = "config.json", db_file: str = "passwords.db"):
"""
初始化安全引擎
Args:
config_file: 配置文件路径
db_file: 数据库文件路径
"""
self.config_file = config_file
self.db_file = db_file
self.key = None
self.salt = None
self.login_attempts = 0
self.locked_until = None
def derive_key(self, password: str, salt: bytes) -> bytes:
"""
使用PBKDF2算法从主密码派生加密密钥(使用hashlib标准库)
Args:
password: 主密码
salt: 随机盐值
Returns:
派生的密钥
"""
# 使用Python标准库的pbkdf2_hmac
key = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
100000, # 迭代次数
dklen=32 # 密钥长度
)
return key
def encrypt_data(self, plaintext: str) -> str:
"""
加密数据(使用简单的基于密钥的加密)
Args:
plaintext: 明文
Returns:
加密后的密文(base64编码)
"""
if not self.key:
raise ValueError("未登录")
# 将明文转换为字节
data = plaintext.encode('utf-8')
# 使用密钥进行简单加密(基于异或操作)
encrypted = bytearray()
for i, byte in enumerate(data):
# 使用密钥的不同字节进行异或加密
key_byte = self.key[i % len(self.key)]
encrypted.append(byte ^ key_byte)
# 添加一些随机数据增加安全性
random_prefix = secrets.token_bytes(16)
encrypted_data = random_prefix + bytes(encrypted)
# Base64编码
return base64.b64encode(encrypted_data).decode('utf-8')
def decrypt_data(self, ciphertext: str) -> str:
"""
解密数据
Args:
ciphertext: 密文(base64编码)
Returns:
解密后的明文
"""
if not self.key:
raise ValueError("未登录")
# Base64解码
encrypted_data = base64.b64decode(ciphertext.encode('utf-8'))
# 移除随机前缀
encrypted = encrypted_data[16:]
# 使用密钥进行解密(基于异或操作)
decrypted = bytearray()
for i, byte in enumerate(encrypted):
# 使用密钥的不同字节进行异或解密
key_byte = self.key[i % len(self.key)]
decrypted.append(byte ^ key_byte)
# 转换为字符串
return bytes(decrypted).decode('utf-8')
def register_master_password(self, password: str) -> bool:
"""
注册主密码
Args:
password: 主密码
Returns:
是否注册成功
"""
try:
# 生成随机盐
salt = os.urandom(16)
# 派生密钥
key = self.derive_key(password, salt)
# 加密固定的校验文本
verification_text = "PASSWORD_MANAGER_VERIFICATION"
encrypted_verification = self.encrypt_data_with_key(verification_text, key)
# 保存配置
config = {
'salt': base64.b64encode(salt).decode(),
'verification': encrypted_verification,
'login_attempts': 0,
'locked_until': None
}
with open(self.config_file, 'w') as f:
json.dump(config, f)
# 初始化数据库
self.init_database()
return True
except Exception as e:
print(f"注册失败: {e}")
return False
def encrypt_data_with_key(self, plaintext: str, key: bytes) -> str:
"""
使用指定密钥加密数据
Args:
plaintext: 明文
key: 密钥
Returns:
加密后的密文
"""
data = plaintext.encode('utf-8')
encrypted = bytearray()
for i, byte in enumerate(data):
key_byte = key[i % len(key)]
encrypted.append(byte ^ key_byte)
random_prefix = secrets.token_bytes(16)
encrypted_data = random_prefix + bytes(encrypted)
return base64.b64encode(encrypted_data).decode('utf-8')
def decrypt_data_with_key(self, ciphertext: str, key: bytes) -> str:
"""
使用指定密钥解密数据
Args:
ciphertext: 密文
key: 密钥
Returns:
解密后的明文
"""
encrypted_data = base64.b64decode(ciphertext.encode('utf-8'))
encrypted = encrypted_data[16:]
decrypted = bytearray()
for i, byte in enumerate(encrypted):
key_byte = key[i % len(key)]
decrypted.append(byte ^ key_byte)
return bytes(decrypted).decode('utf-8')
def login(self, password: str) -> Tuple[bool, str]:
"""
登录验证
Args:
password: 主密码
Returns:
(是否登录成功, 提示信息)
"""
try:
# 检查是否被锁定
if self.is_locked():
remaining = self.get_lock_remaining_time()
return False, f"登录已锁定,请等待 {remaining} 秒"
# 检查配置文件是否存在
if not os.path.exists(self.config_file):
return False, "请先注册主密码"
# 读取配置
with open(self.config_file, 'r') as f:
config = json.load(f)
# 解码盐和校验文本
salt = base64.b64decode(config['salt'])
encrypted_verification = config['verification']
# 派生密钥
key = self.derive_key(password, salt)
# 尝试解密
try:
decrypted = self.decrypt_data_with_key(encrypted_verification, key)
if decrypted == "PASSWORD_MANAGER_VERIFICATION":
# 登录成功,重置错误次数
self.key = key
self.salt = salt
self.login_attempts = 0
self._update_login_attempts(0)
return True, "登录成功"
else:
return self._handle_login_failure()
except Exception:
return self._handle_login_failure()
except Exception as e:
return False, f"登录失败: {e}"
def _handle_login_failure(self) -> Tuple[bool, str]:
"""处理登录失败"""
self.login_attempts += 1
self._update_login_attempts(self.login_attempts)
if self.login_attempts >= 3:
# 锁定5分钟
locked_until = datetime.now() + timedelta(minutes=5)
self._lock_login(locked_until)
return False, "连续错误3次,登录已锁定5分钟"
return False, f"密码错误,还剩 {3 - self.login_attempts} 次机会"
def is_locked(self) -> bool:
"""检查是否被锁定"""
if not os.path.exists(self.config_file):
return False
with open(self.config_file, 'r') as f:
config = json.load(f)
locked_until = config.get('locked_until')
if locked_until:
locked_until = datetime.fromisoformat(locked_until)
if datetime.now() < locked_until:
return True
else:
# 锁定已过期,重置
config['locked_until'] = None
config['login_attempts'] = 0
with open(self.config_file, 'w') as f:
json.dump(config, f)
return False
def get_lock_remaining_time(self) -> int:
"""获取锁定剩余时间(秒)"""
with open(self.config_file, 'r') as f:
config = json.load(f)
locked_until = datetime.fromisoformat(config['locked_until'])
remaining = (locked_until - datetime.now()).total_seconds()
return max(0, int(remaining))
def _update_login_attempts(self, attempts: int):
"""更新登录尝试次数"""
with open(self.config_file, 'r') as f:
config = json.load(f)
config['login_attempts'] = attempts
with open(self.config_file, 'w') as f:
json.dump(config, f)
def _lock_login(self, locked_until: datetime):
"""锁定登录"""
with open(self.config_file, 'r') as f:
config = json.load(f)
config['locked_until'] = locked_until.isoformat()
with open(self.config_file, 'w') as f:
json.dump(config, f)
def init_database(self):
"""初始化数据库"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
notes TEXT,
totp_secret TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def add_password(self, website: str, username: str, password: str,
notes: str = "", totp_secret: str = "") -> bool:
"""
添加密码条目
Args:
website: 网站名称
username: 用户名
password: 密码
notes: 备注
totp_secret: TOTP密钥
Returns:
是否添加成功
"""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
# 加密敏感字段
encrypted_password = self.encrypt_data(password)
encrypted_notes = self.encrypt_data(notes) if notes else ""
encrypted_totp = self.encrypt_data(totp_secret) if totp_secret else ""
cursor.execute('''
INSERT INTO passwords (website, username, password, notes, totp_secret)
VALUES (?, ?, ?, ?, ?)
''', (website, username, encrypted_password, encrypted_notes, encrypted_totp))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"添加密码失败: {e}")
return False
def get_all_passwords(self) -> List[Dict]:
"""
获取所有密码条目
Returns:
密码条目列表
"""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('SELECT * FROM passwords ORDER BY created_at DESC')
rows = cursor.fetchall()
passwords = []
for row in rows:
passwords.append({
'id': row[0],
'website': row[1],
'username': row[2],
'password': self.decrypt_data(row[3]),
'notes': self.decrypt_data(row[4]) if row[4] else "",
'totp_secret': self.decrypt_data(row[5]) if row[5] else "",
'created_at': row[6],
'updated_at': row[7]
})
conn.close()
return passwords
except Exception as e:
print(f"获取密码失败: {e}")
return []
def update_password(self, id: int, website: str, username: str,
password: str, notes: str = "", totp_secret: str = "") -> bool:
"""
更新密码条目
Args:
id: 条目ID
website: 网站名称
username: 用户名
password: 密码
notes: 备注
totp_secret: TOTP密钥
Returns:
是否更新成功
"""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
# 加密敏感字段
encrypted_password = self.encrypt_data(password)
encrypted_notes = self.encrypt_data(notes) if notes else ""
encrypted_totp = self.encrypt_data(totp_secret) if totp_secret else ""
cursor.execute('''
UPDATE passwords
SET website=?, username=?, password=?, notes=?, totp_secret=?, updated_at=CURRENT_TIMESTAMP
WHERE id=?
''', (website, username, encrypted_password, encrypted_notes, encrypted_totp, id))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"更新密码失败: {e}")
return False
def delete_password(self, id: int) -> bool:
"""
删除密码条目
Args:
id: 条目ID
Returns:
是否删除成功
"""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('DELETE FROM passwords WHERE id=?', (id,))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"删除密码失败: {e}")
return False
@staticmethod
def generate_password(length: int = 16, use_uppercase: bool = True,
use_lowercase: bool = True, use_digits: bool = True,
use_special: bool = True, exclude_similar: bool = True) -> str:
"""
生成强密码
Args:
length: 密码长度
use_uppercase: 是否包含大写字母
use_lowercase: 是否包含小写字母
use_digits: 是否包含数字
use_special: 是否包含特殊字符
exclude_similar: 是否排除易混淆字符
Returns:
生成的密码
"""
characters = ""
if use_uppercase:
characters += string.ascii_uppercase
if use_lowercase:
characters += string.ascii_lowercase
if use_digits:
characters += string.digits
if use_special:
characters += "!@#$%^&*()_+-=[]{}|;:,.<>?"
# 排除易混淆字符
if exclude_similar:
similar_chars = "Il1O0"
characters = ''.join(c for c in characters if c not in similar_chars)
if not characters:
characters = string.ascii_letters + string.digits
# 确保密码包含至少一个每种选定的字符类型
password = []
if use_uppercase and any(c in string.ascii_uppercase for c in characters):
password.append(secrets.choice([c for c in characters if c in string.ascii_uppercase]))
if use_lowercase and any(c in string.ascii_lowercase for c in characters):
password.append(secrets.choice([c for c in characters if c in string.ascii_lowercase]))
if use_digits and any(c in string.digits for c in characters):
password.append(secrets.choice([c for c in characters if c in string.digits]))
if use_special and any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in characters):
password.append(secrets.choice([c for c in characters if c in "!@#$%^&*()_+-=[]{}|;:,.<>?"]))
# 填充剩余长度
remaining_length = length - len(password)
password.extend(secrets.choice(characters) for _ in range(remaining_length))
# 打乱顺序
secrets.SystemRandom().shuffle(password)
return ''.join(password)
@staticmethod
def calculate_password_strength(password: str) -> Tuple[int, str, str]:
"""
计算密码强度
Args:
password: 密码
Returns:
(强度分数0-100, 强度等级, 颜色代码)
"""
if not password:
return 0, "弱", "red"
# 计算熵值
pool_size = 0
if any(c.islower() for c in password):
pool_size += 26
if any(c.isupper() for c in password):
pool_size += 26
if any(c.isdigit() for c in password):
pool_size += 10
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
pool_size += 20
if pool_size == 0:
return 0, "弱", "red"
entropy = len(password) * math.log2(pool_size)
# 根据熵值计算强度分数
if entropy < 28:
score = 20
level = "弱"
color = "red"
elif entropy < 36:
score = 40
level = "中"
color = "orange"
elif entropy < 60:
score = 70
level = "强"
color = "yellow"
else:
score = 100
level = "很强"
color = "green"
# 根据长度调整分数
length_bonus = min(len(password) * 2, 20)
score = min(score + length_bonus, 100)
return score, level, color
@staticmethod
def generate_totp(secret: str) -> Tuple[str, int]:
"""
生成TOTP动态验证码(使用Python标准库实现)
Args:
secret: Base32格式的TOTP密钥
Returns:
(6位验证码, 剩余有效秒数)
"""
try:
# 解码Base32密钥
# 移除可能的空格和填充
secret = secret.replace(' ', '').replace('-', '').upper()
# 添加填充
padding = len(secret) % 8
if padding != 0:
secret += '=' * (8 - padding)
# Base32解码
key = base64.b32decode(secret)
# 获取当前时间戳(30秒时间步长)
current_time = int(time.time())
time_step = current_time // 30
# 将时间步转换为字节(8字节,大端序)
time_bytes = struct.pack('>Q', time_step)
# 使用HMAC-SHA1生成哈希
hmac_hash = hmac.new(key, time_bytes, hashlib.sha1).digest()
# 动态截断
offset = hmac_hash[-1] & 0x0F
code_bytes = hmac_hash[offset:offset + 4]
code_int = struct.unpack('>I', code_bytes)[0]
# 取模得到6位数字
code = (code_int & 0x7FFFFFFF) % 1000000
# 格式化为6位字符串
code_str = str(code).zfill(6)
# 计算剩余有效时间
remaining_seconds = 30 - (current_time % 30)
return code_str, remaining_seconds
except Exception as e:
print(f"生成TOTP失败: {e}")
return "------", 0
@staticmethod
def copy_to_clipboard(text: str):
"""
复制文本到剪贴板(使用tkinter内置功能)
Args:
text: 要复制的文本
"""
# 创建一个临时的tkinter窗口
root = tk.Tk()
root.withdraw() # 隐藏窗口
root.clipboard_clear()
root.clipboard_append(text)
root.update() # 确保剪贴板内容被更新
root.destroy()
@staticmethod
def clear_clipboard():
"""清空剪贴板"""
try:
root = tk.Tk()
root.withdraw()
root.clipboard_clear()
root.clipboard_append("")
root.update()
root.destroy()
except:
pass
# ==================== 图形界面类 ====================
class PasswordManagerApp:
"""密码管理系统主应用"""
def __init__(self):
self.engine = SecurityEngine()
self.root = tk.Tk()
self.root.title("密码管理系统")
self.root.geometry("1200x800")
self.root.configure(bg="#f0f0f0")
# 存储密码数据的字典(用于操作列功能)
self.password_data = {} # 格式:{item_id: {'password': '...', 'totp': '...'}}
# 设置样式
self.style = ttk.Style()
self.style.theme_use('clam')
# 配置样式
self.configure_styles()
# 检查是否需要初始化
if not os.path.exists("config.json"):
self.show_init_dialog()
# 显示登录窗口
self.show_login_window()
def configure_styles(self):
"""配置界面样式 - 美观现代化设计"""
# 现代配色方案
colors = {
'primary': '#667eea', # 主色调 - 紫蓝渐变
'secondary': '#764ba2', # 次色调 - 紫色
'success': '#48bb78', # 成功 - 绿色
'warning': '#f6ad55', # 警告 - 橙色
'danger': '#fc8181', # 危险 - 红色
'info': '#4299e1', # 信息 - 蓝色
'light': '#f7fafc', # 浅色背景
'dark': '#2d3748', # 深色文字
'white': '#ffffff', # 白色
'gradient_start': '#667eea', # 渐变起始色
'gradient_end': '#764ba2', # 渐变结束色
}
# 主按钮样式 - 现代圆角设计
self.style.configure('Main.TButton',
font=('Segoe UI', 11, 'bold'),
padding=(20, 10),
foreground='white',
background=colors['primary'])
# 按钮悬停效果
self.style.map('Main.TButton',
background=[('active', colors['secondary'])])
# 成功按钮样式
self.style.configure('Success.TButton',
font=('Segoe UI', 11, 'bold'),
padding=(20, 10),
foreground='white',
background=colors['success'])
# 工具按钮样式
self.style.configure('Tool.TButton',
font=('Segoe UI', 10),
padding=(15, 8),
foreground=colors['dark'],
background=colors['light'])
# 标题样式 - 大字体醒目
self.style.configure('Title.TLabel',
font=('Segoe UI', 18, 'bold'),
foreground=colors['primary'])
# 大标题样式
self.style.configure('BigTitle.TLabel',
font=('Segoe UI', 24, 'bold'),
foreground=colors['gradient_start'])
# 状态样式
self.style.configure('Status.TLabel',
font=('Segoe UI', 11),
foreground=colors['danger'])
# 成功状态样式
self.style.configure('Success.TLabel',
font=('Segoe UI', 11, 'bold'),
foreground=colors['success'])
# 信息样式
self.style.configure('Info.TLabel',
font=('Segoe UI', 10),
foreground=colors['info'])
# 普通标签样式
self.style.configure('Normal.TLabel',
font=('Segoe UI', 10),
foreground=colors['dark'])
# 输入框样式
self.style.configure('Modern.TEntry',
font=('Segoe UI', 11),
padding=10)
# 框架样式
self.style.configure('Card.TFrame',
background=colors['white'])
# 标签框架样式
self.style.configure('Modern.TLabelframe',
background=colors['white'],
foreground=colors['dark'])
self.style.configure('Modern.TLabelframe.Label',
font=('Segoe UI', 11, 'bold'),
foreground=colors['primary'],
background=colors['white'])
# 进度条样式
self.style.configure('Modern.Horizontal.TProgressbar',
background=colors['success'],
troughcolor=colors['light'])
# Treeview样式
self.style.configure('Modern.Treeview',
font=('Segoe UI', 10),
background=colors['white'],
foreground=colors['dark'],
fieldbackground=colors['white'])
self.style.configure('Modern.Treeview.Heading',
font=('Segoe UI', 10, 'bold'),
background=colors['primary'],
foreground='white')
# Scrollbar样式
self.style.configure('Modern.Vertical.TScrollbar',
background=colors['light'],
troughcolor=colors['white'])
def show_init_dialog(self):
"""显示初始化对话框 - 美观现代化设计"""
init_window = tk.Toplevel(self.root)
init_window.title("系统初始化")
init_window.geometry("600x650") # 进一步增加窗口高度
init_window.configure(bg="#667eea") # 渐变背景色
init_window.transient(self.root)
init_window.grab_set()
# 创建主容器
main_frame = tk.Frame(init_window, bg="#ffffff", padx=30, pady=25) # 进一步减少padding
main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15) # 减少外部padding
# 标题区域 - 大图标和标题
title_frame = tk.Frame(main_frame, bg="#ffffff")
title_frame.pack(fill=tk.X, pady=(0, 15)) # 减少padding
# 大图标
icon_label = tk.Label(title_frame,
text="🔐",
font=('Segoe UI', 40), # 减小图标大小
bg="#ffffff",
fg="#667eea")
icon_label.pack(pady=(0, 10))
# 大标题
title_label = tk.Label(title_frame,
text="欢迎使用密码管理系统",
font=('Segoe UI', 18, 'bold'), # 减小字体
bg="#ffffff",
fg="#2d3748")
title_label.pack()
# 说明文字
info_label = tk.Label(title_frame,
text="首次运行,请设置主密码以保护您的数据",
font=('Segoe UI', 11),
bg="#ffffff",
fg="#718096")
info_label.pack(pady=(5, 0))
# 输入区域
input_frame = tk.Frame(main_frame, bg="#ffffff")
input_frame.pack(fill=tk.X, pady=15) # 减少padding
# 主密码输入框 - 美观设计
tk.Label(input_frame,
text="主密码(至少8位)",
font=('Segoe UI', 11, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
password_var = tk.StringVar()
password_entry = tk.Entry(input_frame,
textvariable=password_var,
show="●", # 使用圆点符号
font=('Segoe UI', 12),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
password_entry.pack(fill=tk.X, ipady=10, pady=(0, 10)) # 减少padding
# 确认密码输入框
tk.Label(input_frame,
text="确认密码",
font=('Segoe UI', 11, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
confirm_var = tk.StringVar()
confirm_entry = tk.Entry(input_frame,
textvariable=confirm_var,
show="●",
font=('Segoe UI', 12),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
confirm_entry.pack(fill=tk.X, ipady=10, pady=(0, 10)) # 减少padding
# 警告提示 - 美观警告框
warning_frame = tk.Frame(main_frame, bg="#fff5f5", padx=15, pady=10)
warning_frame.pack(fill=tk.X, pady=10)
warning_label = tk.Label(warning_frame,
text="⚠️ 重要提示:请牢记主密码,忘记后将无法恢复数据!",
font=('Segoe UI', 10),
bg="#fff5f5",
fg="#c53030")
warning_label.pack()
# 状态标签
status_var = tk.StringVar()
status_label = tk.Label(main_frame,
textvariable=status_var,
font=('Segoe UI', 10),
bg="#ffffff",
fg="#fc8181")
status_label.pack(pady=10)
# 初始化按钮 - 美观大按钮(放在main_frame内)
def initialize():
password1 = password_var.get()
password2 = confirm_var.get()
if not password1 or not password2:
status_var.set("❌ 请输入主密码")
return
if len(password1) < 8:
status_var.set("❌ 密码长度至少8位")
return
if password1 != password2:
status_var.set("❌ 两次密码不一致")
return
# 执行初始化
success = self.engine.register_master_password(password1)
if success:
messagebox.showinfo("✅ 成功", "系统初始化成功!\n请使用主密码登录。")
init_window.destroy()
else:
status_var.set("❌ 初始化失败,请重试")
init_button = tk.Button(main_frame, # 放在main_frame内
text="✅ 开始初始化", # 添加图标
command=initialize,
font=('Segoe UI', 14, 'bold'), # 增大字体
bg="#48bb78",
fg="white",
activebackground="#38a169",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
height=2) # 设置按钮高度
init_button.pack(fill=tk.X, pady=15) # 增加padding
# 等待窗口关闭
self.root.wait_window(init_window)
def show_login_window(self):
"""显示登录窗口 - 美观现代化设计"""
# 清空主窗口
for widget in self.root.winfo_children():
widget.destroy()
self.root.geometry("550x650") # 增加窗口高度
self.root.configure(bg="#667eea") # 渐变背景
# 创建主容器 - 白色卡片
main_frame = tk.Frame(self.root, bg="#ffffff", padx=40, pady=35) # 减少padding
main_frame.pack(fill=tk.BOTH, expand=True, padx=25, pady=25) # 减少外部padding
# 标题区域
title_frame = tk.Frame(main_frame, bg="#ffffff")
title_frame.pack(fill=tk.X, pady=(0, 20)) # 减少padding
# 大图标
icon_label = tk.Label(title_frame,
text="🔐",
font=('Segoe UI', 48), # 减小图标大小
bg="#ffffff",
fg="#667eea")
icon_label.pack(pady=(0, 15))
# 大标题
title_label = tk.Label(title_frame,
text="密码管理系统",
font=('Segoe UI', 20, 'bold'), # 减小字体
bg="#ffffff",
fg="#2d3748")
title_label.pack()
# 副标题
subtitle_label = tk.Label(title_frame,
text="安全 · 简单 · 可信赖",
font=('Segoe UI', 11),
bg="#ffffff",
fg="#718096")
subtitle_label.pack(pady=(5, 0))
# 输入区域
input_frame = tk.Frame(main_frame, bg="#ffffff")
input_frame.pack(fill=tk.X, pady=15) # 减少padding
# 主密码输入框 - 美观设计
tk.Label(input_frame,
text="主密码",
font=('Segoe UI', 11, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
password_var = tk.StringVar()
password_entry = tk.Entry(input_frame,
textvariable=password_var,
show="●",
font=('Segoe UI', 12),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
password_entry.pack(fill=tk.X, ipady=10, pady=(0, 15)) # 减少padding
password_entry.focus()
# 状态标签
status_var = tk.StringVar()
status_label = tk.Label(main_frame,
textvariable=status_var,
font=('Segoe UI', 10),
bg="#ffffff",
fg="#fc8181")
status_label.pack(pady=10)
# 登录按钮 - 美观大按钮
def login():
password = password_var.get()
if not password:
status_var.set("❌ 请输入主密码")
return
success, message = self.engine.login(password)
if success:
status_var.set("✅ 登录成功!")
status_label.configure(fg="#48bb78")
login_button.configure(bg="#48bb78")
self.root.after(500, self.show_main_window)
else:
status_var.set(f"❌ {message}")
password_var.set("")
login_button = tk.Button(main_frame,
text="✅ 登录", # 添加图标
command=login,
font=('Segoe UI', 14, 'bold'), # 增大字体
bg="#667eea",
fg="white",
activebackground="#764ba2",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
height=2) # 设置按钮高度
login_button.pack(fill=tk.X, pady=15) # 增加padding
# 检查锁定状态
self.check_lock_status(status_var, login_button)
def check_lock_status(self, status_var, login_button):
"""检查锁定状态"""
if self.engine.is_locked():
remaining = self.engine.get_lock_remaining_time()
status_var.set(f"登录已锁定,请等待 {remaining} 秒")
login_button.configure(state='disabled')
# 定时检查
self.root.after(1000, lambda: self.check_lock_status(status_var, login_button))
else:
login_button.configure(state='normal')
def show_main_window(self):
"""显示主窗口 - 美观现代化设计"""
# 清空主窗口
for widget in self.root.winfo_children():
widget.destroy()
self.root.geometry("1200x1000") # 调整窗口宽度到1200像素,删除备注列后宽度足够
self.root.configure(bg="#f7fafc") # 浅色背景
# 创建顶部标题栏
header_frame = tk.Frame(self.root, bg="#667eea", height=80)
header_frame.pack(fill=tk.X)
header_frame.pack_propagate(False)
# 标题栏内容
header_content = tk.Frame(header_frame, bg="#667eea")
header_content.pack(fill=tk.BOTH, expand=True, padx=30, pady=20)
# 左侧标题
title_frame = tk.Frame(header_content, bg="#667eea")
title_frame.pack(side=tk.LEFT)
tk.Label(title_frame,
text="🔐",
font=('Segoe UI', 28),
bg="#667eea",
fg="white").pack(side=tk.LEFT, padx=(0, 10))
tk.Label(title_frame,
text="密码管理系统",
font=('Segoe UI', 18, 'bold'),
bg="#667eea",
fg="white").pack(side=tk.LEFT)
# 右侧信息
info_frame = tk.Frame(header_content, bg="#667eea")
info_frame.pack(side=tk.RIGHT)
tk.Label(info_frame,
text="安全 · 简单 · 可信赖",
font=('Segoe UI', 11),
bg="#667eea",
fg="white").pack(side=tk.RIGHT)
# 创建主内容区域
main_frame = tk.Frame(self.root, bg="#f7fafc")
main_frame.pack(fill=tk.BOTH, expand=True, padx=30, pady=30)
# 右侧:工具面板(固定宽度,添加滚动条)- 先pack右侧面板,确保固定宽度
right_frame = tk.Frame(main_frame, bg="#ffffff", padx=5, pady=5, width=450) # 调整宽度到450像素
right_frame.pack(side=tk.RIGHT, fill=tk.Y) # 只填充Y方向,不扩展
right_frame.pack_propagate(False) # 防止自动调整大小,保持固定宽度
# 创建Canvas和滚动条
right_canvas = tk.Canvas(right_frame, bg="#ffffff", highlightthickness=0, width=430) # 设置Canvas宽度为430像素
right_scrollbar = ttk.Scrollbar(right_frame, orient=tk.VERTICAL, command=right_canvas.yview,
style='Modern.Vertical.TScrollbar')
# 创建可滚动的内容框架
right_scrollable_frame = tk.Frame(right_canvas, bg="#ffffff")
# 绑定Configure事件,更新滚动区域
right_scrollable_frame.bind(
"<Configure>",
lambda e: right_canvas.configure(scrollregion=right_canvas.bbox("all"))
)
# 在Canvas中创建窗口
right_canvas.create_window((0, 0), window=right_scrollable_frame, anchor="nw", width=430) # 设置窗口宽度为430像素
# 配置Canvas的滚动命令
right_canvas.configure(yscrollcommand=right_scrollbar.set)
# 显示Canvas和滚动条
right_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
right_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 绑定鼠标滚轮事件到Canvas
right_canvas.bind("<MouseWheel>", self._on_mousewheel)
right_scrollable_frame.bind("<MouseWheel>", self._on_mousewheel)
# 剪贴板状态 - 美观卡片(移到顶部,用户能直接看到)
self.create_clipboard_panel(right_scrollable_frame)
# 密码生成器 - 美观卡片
self.create_generator_panel(right_scrollable_frame)
# 密码强度评估 - 美观卡片
self.create_strength_panel(right_scrollable_frame)
# 左侧:密码列表 - 白色卡片 - 后pack左侧面板,填充剩余空间
left_frame = tk.Frame(main_frame, bg="#ffffff", padx=20, pady=20)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 15)) # 填充剩余空间
# 密码库标题
tk.Label(left_frame,
text="密码库",
font=('Segoe UI', 16, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 15))
# 工具栏 - 美观按钮组
toolbar_frame = tk.Frame(left_frame, bg="#ffffff")
toolbar_frame.pack(fill=tk.X, pady=(0, 15))
# 添加按钮 - 绿色
add_button = tk.Button(toolbar_frame,
text="➕ 添加",
command=self.add_password,
font=('Segoe UI', 10, 'bold'),
bg="#48bb78",
fg="white",
activebackground="#38a169",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
padx=15, pady=8)
add_button.pack(side=tk.LEFT, padx=(0, 10))
# 编辑按钮 - 蓝色
edit_button = tk.Button(toolbar_frame,
text="✏️ 编辑",
command=self.edit_password,
font=('Segoe UI', 10, 'bold'),
bg="#4299e1",
fg="white",
activebackground="#3182ce",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
padx=15, pady=8)
edit_button.pack(side=tk.LEFT, padx=(0, 10))
# 删除按钮 - 红色
delete_button = tk.Button(toolbar_frame,
text="🗑️ 删除",
command=self.delete_password,
font=('Segoe UI', 10, 'bold'),
bg="#fc8181",
fg="white",
activebackground="#f56565",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
padx=15, pady=8)
delete_button.pack(side=tk.LEFT, padx=(0, 10))
# 刷新按钮 - 灰色
refresh_button = tk.Button(toolbar_frame,
text="🔄 刷新",
command=self.load_passwords,
font=('Segoe UI', 10, 'bold'),
bg="#718096",
fg="white",
activebackground="#4a5568",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
padx=15, pady=8)
refresh_button.pack(side=tk.LEFT)
# 密码列表表格 - 美观样式
columns = ('序号', '网站', '用户名', '密码', 'TOTP', '操作')
self.tree = ttk.Treeview(left_frame, columns=columns, show='headings', height=25, style='Modern.Treeview')
# 设置列标题 - 美观样式
self.tree.heading('序号', text='序号')
self.tree.heading('网站', text='网站')
self.tree.heading('用户名', text='用户名')
self.tree.heading('密码', text='密码')
self.tree.heading('TOTP', text='TOTP验证码')
self.tree.heading('操作', text='操作')
# 设置列宽
self.tree.column('序号', width=60, anchor=tk.CENTER)
self.tree.column('网站', width=150, anchor=tk.W) # 增加宽度
self.tree.column('用户名', width=150, anchor=tk.W) # 增加宽度
self.tree.column('密码', width=150, anchor=tk.CENTER) # 增加宽度
self.tree.column('TOTP', width=150, anchor=tk.CENTER) # 增加宽度
self.tree.column('操作', width=180, anchor=tk.CENTER) # 增加操作列宽度到180像素
# 添加滚动条 - 美观样式
scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=self.tree.yview,
style='Modern.Vertical.TScrollbar')
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.load_passwords()
# 启动TOTP更新定时器
self.update_totp_codes()
def create_generator_panel(self, parent):
"""创建密码生成器面板 - 美观现代化设计"""
# 创建卡片容器
gen_frame = tk.Frame(parent, bg="#ffffff")
gen_frame.pack(fill=tk.X, pady=(0, 15))
# 标题区域 - 紫色背景
title_frame = tk.Frame(gen_frame, bg="#667eea", padx=10, pady=8) # 减少padding
title_frame.pack(fill=tk.X)
tk.Label(title_frame,
text="🎲 密码生成器",
font=('Segoe UI', 12, 'bold'),
bg="#667eea",
fg="white").pack(anchor=tk.W)
# 内容区域
content_frame = tk.Frame(gen_frame, bg="#ffffff", padx=10, pady=10) # 减少padding
content_frame.pack(fill=tk.BOTH, expand=True)
# 密码长度 - 美观设计
length_frame = tk.Frame(content_frame, bg="#ffffff")
length_frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(length_frame,
text="密码长度",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(side=tk.LEFT)
self.length_var = tk.IntVar(value=16)
length_spinbox = tk.Spinbox(length_frame,
from_=8,
to=64,
textvariable=self.length_var,
font=('Segoe UI', 10),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
width=8,
buttonbackground="#667eea")
length_spinbox.pack(side=tk.RIGHT)
# 字符选项 - 美观复选框
options_frame = tk.Frame(content_frame, bg="#f7fafc", padx=10, pady=10)
options_frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(options_frame,
text="字符选项",
font=('Segoe UI', 10, 'bold'),
bg="#f7fafc",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
self.uppercase_var = tk.BooleanVar(value=True)
tk.Checkbutton(options_frame,
text="大写字母 (A-Z)",
variable=self.uppercase_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#2d3748",
selectcolor="#667eea",
activebackground="#f7fafc",
activeforeground="#2d3748").pack(anchor=tk.W, pady=2)
self.lowercase_var = tk.BooleanVar(value=True)
tk.Checkbutton(options_frame,
text="小写字母 (a-z)",
variable=self.lowercase_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#2d3748",
selectcolor="#667eea",
activebackground="#f7fafc",
activeforeground="#2d3748").pack(anchor=tk.W, pady=2)
self.digits_var = tk.BooleanVar(value=True)
tk.Checkbutton(options_frame,
text="数字 (0-9)",
variable=self.digits_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#2d3748",
selectcolor="#667eea",
activebackground="#f7fafc",
activeforeground="#2d3748").pack(anchor=tk.W, pady=2)
self.special_var = tk.BooleanVar(value=True)
tk.Checkbutton(options_frame,
text="特殊字符 (!@#$)",
variable=self.special_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#2d3748",
selectcolor="#667eea",
activebackground="#f7fafc",
activeforeground="#2d3748").pack(anchor=tk.W, pady=2)
self.exclude_similar_var = tk.BooleanVar(value=True)
tk.Checkbutton(options_frame,
text="排除易混淆字符",
variable=self.exclude_similar_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#2d3748",
selectcolor="#667eea",
activebackground="#f7fafc",
activeforeground="#2d3748").pack(anchor=tk.W, pady=2)
# 生成按钮 - 美观大按钮
generate_button = tk.Button(content_frame,
text="🎲 生成密码",
command=self.generate_password,
font=('Segoe UI', 10, 'bold'),
bg="#667eea",
fg="white",
activebackground="#764ba2",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2")
generate_button.pack(fill=tk.X, pady=(0, 10), ipady=8)
# 生成的密码显示 - 美观输入框
self.generated_password_var = tk.StringVar()
password_entry = tk.Entry(content_frame,
textvariable=self.generated_password_var,
font=('Segoe UI', 11, 'bold'),
bg="#f7fafc",
fg="#667eea",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
password_entry.pack(fill=tk.X, pady=(0, 10), ipady=8)
# 复制按钮 - 美观按钮
copy_button = tk.Button(content_frame,
text="📋 复制密码",
command=self.copy_generated_password,
font=('Segoe UI', 10, 'bold'),
bg="#48bb78",
fg="white",
activebackground="#38a169",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2")
copy_button.pack(fill=tk.X, ipady=8)
def create_strength_panel(self, parent):
"""创建密码强度评估面板 - 美观现代化设计"""
# 创建卡片容器
strength_frame = tk.Frame(parent, bg="#ffffff")
strength_frame.pack(fill=tk.X, pady=(0, 15))
# 标题区域 - 蓝色背景
title_frame = tk.Frame(strength_frame, bg="#4299e1", padx=10, pady=8) # 减少padding
title_frame.pack(fill=tk.X)
tk.Label(title_frame,
text="💪 密码强度评估",
font=('Segoe UI', 12, 'bold'),
bg="#4299e1",
fg="white").pack(anchor=tk.W)
# 内容区域
content_frame = tk.Frame(strength_frame, bg="#ffffff", padx=10, pady=10) # 减少padding
content_frame.pack(fill=tk.BOTH, expand=True)
# 密码输入 - 美观设计
tk.Label(content_frame,
text="测试密码",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
self.test_password_var = tk.StringVar()
password_entry = tk.Entry(content_frame,
textvariable=self.test_password_var,
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#4299e1")
password_entry.pack(fill=tk.X, pady=(0, 10), ipady=8)
password_entry.bind('<KeyRelease>', self.update_strength)
# 评估按钮 - 美观大按钮
evaluate_button = tk.Button(content_frame,
text="💪 评估强度",
command=self.update_strength,
font=('Segoe UI', 11, 'bold'),
bg="#4299e1",
fg="white",
activebackground="#3182ce",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
height=1)
evaluate_button.pack(fill=tk.X, pady=(0, 10), ipady=8)
# 强度进度条 - 美观设计
tk.Label(content_frame,
text="强度等级",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 5))
self.strength_progress = ttk.Progressbar(content_frame,
length=200,
mode='determinate',
maximum=100,
style='Modern.Horizontal.TProgressbar')
self.strength_progress.pack(fill=tk.X, pady=(0, 10))
# 强度标签 - 美观显示
self.strength_label_var = tk.StringVar(value="密码强度: 未测试")
strength_label = tk.Label(content_frame,
textvariable=self.strength_label_var,
font=('Segoe UI', 11, 'bold'),
bg="#ffffff",
fg="#718096")
strength_label.pack(pady=(0, 10))
# 建议标签 - 美观提示
self.suggestion_var = tk.StringVar(value="请输入密码进行强度评估")
suggestion_label = tk.Label(content_frame,
textvariable=self.suggestion_var,
font=('Segoe UI', 9),
bg="#f7fafc",
fg="#4a5568",
wraplength=300,
padx=10,
pady=10)
suggestion_label.pack(fill=tk.X)
def create_clipboard_panel(self, parent):
"""创建剪贴板状态面板 - 美观现代化设计"""
# 创建卡片容器
clipboard_frame = tk.Frame(parent, bg="#ffffff")
clipboard_frame.pack(fill=tk.X, pady=(0, 15))
# 标题区域 - 绿色背景
title_frame = tk.Frame(clipboard_frame, bg="#48bb78", padx=10, pady=8) # 减少padding
title_frame.pack(fill=tk.X)
tk.Label(title_frame,
text="📋 剪贴板状态",
font=('Segoe UI', 12, 'bold'),
bg="#48bb78",
fg="white").pack(anchor=tk.W)
# 内容区域
content_frame = tk.Frame(clipboard_frame, bg="#ffffff", padx=10, pady=10) # 减少padding
content_frame.pack(fill=tk.BOTH, expand=True)
# 状态标签 - 美观显示
self.clipboard_status_var = tk.StringVar(value="剪贴板状态: 空")
status_label = tk.Label(content_frame,
textvariable=self.clipboard_status_var,
font=('Segoe UI', 11, 'bold'),
bg="#ffffff",
fg="#718096")
status_label.pack(pady=(0, 10))
# 倒计时标签 - 美观醒目
self.clipboard_countdown_var = tk.StringVar(value="")
countdown_label = tk.Label(content_frame,
textvariable=self.clipboard_countdown_var,
font=('Segoe UI', 14, 'bold'),
bg="#fff5f5",
fg="#fc8181",
padx=10,
pady=10)
countdown_label.pack(fill=tk.X)
def load_passwords(self):
"""加载密码列表"""
# 清空表格和字典
for item in self.tree.get_children():
self.tree.delete(item)
self.password_data.clear() # 清空密码数据字典
# 获取所有密码
passwords = self.engine.get_all_passwords()
# 添加到表格(使用行号显示)
row_number = 1
for entry in passwords:
# 隐藏密码
password_display = "******"
# TOTP显示
if entry['totp_secret']:
totp_code, remaining = SecurityEngine.generate_totp(entry['totp_secret'])
totp_display = f"{totp_code} ({remaining}s)"
else:
totp_display = "-"
# 添加行(使用行号而不是ID)
item_id = self.tree.insert('', tk.END, values=(
row_number, # 显示行号
entry['website'],
entry['username'],
password_display,
totp_display,
"显示 | 复制" # 简化操作列文字,去掉emoji图标
))
# 存储实际密码和TOTP密钥到字典(仍然保存ID用于删除操作)
self.password_data[item_id] = {
'password': entry['password'],
'totp': entry['totp_secret'],
'id': entry['id'] # 保存实际ID用于删除操作
}
row_number += 1
# 绑定双击事件
self.tree.bind('<Double-1>', self.on_tree_double_click)
def on_tree_double_click(self, event):
"""处理表格双击事件"""
# 获取点击的列
region = self.tree.identify_region(event.x, event.y)
if region == "cell":
column = self.tree.identify_column(event.x)
item = self.tree.identify_row(event.y)
if column == '#6': # 操作列(现在是第6列)
# 从字典中获取实际密码
if item in self.password_data:
password = self.password_data[item]['password']
# 显示菜单
menu = tk.Menu(self.root, tearoff=0)
menu.add_command(label="显示密码",
command=lambda: self.show_password(item))
menu.add_command(label="隐藏密码",
command=lambda: self.hide_password(item))
menu.add_command(label="复制密码",
command=lambda: self.copy_password_to_clipboard(password))
menu.post(event.x_root, event.y_root)
def show_password(self, item):
"""显示密码"""
if item in self.password_data:
password = self.password_data[item]['password']
# 更新表格显示(6列数据)
values = self.tree.item(item, 'values')
new_values = (values[0], values[1], values[2], password, values[4], values[5])
self.tree.item(item, values=new_values)
def hide_password(self, item):
"""隐藏密码"""
# 更新表格显示(6列数据)
values = self.tree.item(item, 'values')
new_values = (values[0], values[1], values[2], "******", values[4], values[5])
self.tree.item(item, values=new_values)
def copy_password_to_clipboard(self, password):
"""复制密码到剪贴板"""
SecurityEngine.copy_to_clipboard(password)
# 更新剪贴板状态
self.clipboard_status_var.set("剪贴板状态: 已复制密码")
# 启动30秒倒计时
self.start_clipboard_countdown(30)
messagebox.showinfo("成功", "密码已复制到剪贴板!\n将在30秒后自动清空。")
def start_clipboard_countdown(self, seconds):
"""启动剪贴板倒计时"""
self.clipboard_seconds = seconds
self.update_clipboard_countdown()
def update_clipboard_countdown(self):
"""更新剪贴板倒计时"""
self.clipboard_seconds -= 1
self.clipboard_countdown_var.set(f"倒计时: {self.clipboard_seconds} 秒后清空")
if self.clipboard_seconds <= 0:
# 清空剪贴板
SecurityEngine.clear_clipboard()
self.clipboard_status_var.set("剪贴板状态: 空")
self.clipboard_countdown_var.set("")
else:
# 继续倒计时
self.root.after(1000, self.update_clipboard_countdown)
def _on_mousewheel(self, event):
"""处理鼠标滚轮事件"""
# 获取右侧Canvas
for widget in self.root.winfo_children():
if isinstance(widget, tk.Frame):
for child in widget.winfo_children():
if isinstance(child, tk.Frame):
for subchild in child.winfo_children():
if isinstance(subchild, tk.Canvas):
subchild.yview_scroll(int(-1 * (event.delta / 120)), "units")
return
def update_totp_codes(self):
"""更新TOTP验证码"""
for item in self.tree.get_children():
totp_secret = self.tree.set(item, 'totp_real')
if totp_secret:
totp_code, remaining = SecurityEngine.generate_totp(totp_secret)
self.tree.set(item, 'TOTP', f"{totp_code} ({remaining}s)")
# 每秒更新
self.root.after(1000, self.update_totp_codes)
def add_password(self):
"""添加密码"""
self.show_add_edit_dialog()
def edit_password(self):
"""编辑密码"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择要编辑的条目!")
return
item = selected[0]
# 从字典中获取实际ID
if item in self.password_data:
entry_id = self.password_data[item]['id']
# 获取当前数据
passwords = self.engine.get_all_passwords()
entry = next((p for p in passwords if p['id'] == entry_id), None)
if entry:
self.show_add_edit_dialog(entry)
else:
messagebox.showerror("错误", "无法获取密码数据!")
def delete_password(self):
"""删除密码"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择要删除的条目!")
return
item = selected[0]
# 从字典中获取实际ID
if item in self.password_data:
entry_id = self.password_data[item]['id']
if messagebox.askyesno("确认删除", "确定要删除此密码条目吗?此操作不可恢复。"):
success = self.engine.delete_password(entry_id)
if success:
messagebox.showinfo("成功", "密码删除成功!")
self.load_passwords() # 重新加载列表,行号会自动更新
else:
messagebox.showerror("错误", "密码删除失败!")
else:
messagebox.showerror("错误", "无法获取密码数据!")
def show_add_edit_dialog(self, entry=None):
"""显示添加/编辑对话框 - 美观现代化设计"""
dialog = tk.Toplevel(self.root)
dialog.title("添加密码" if not entry else "编辑密码")
dialog.geometry("650x700") # 进一步增加窗口大小
dialog.configure(bg="#667eea") # 渐变背景
dialog.transient(self.root)
dialog.grab_set()
# 创建主容器 - 白色卡片
main_frame = tk.Frame(dialog, bg="#ffffff", padx=30, pady=25) # 进一步减少padding
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 进一步减少外部padding
# 标题区域
title_frame = tk.Frame(main_frame, bg="#ffffff")
title_frame.pack(fill=tk.X, pady=(0, 15)) # 减少padding
# 大图标
icon_label = tk.Label(title_frame,
text="🔐" if not entry else "✏️",
font=('Segoe UI', 32), # 减小图标大小
bg="#ffffff",
fg="#667eea")
icon_label.pack(pady=(0, 8)) # 减少padding
# 大标题
title_label = tk.Label(title_frame,
text="添加新密码" if not entry else "编辑密码",
font=('Segoe UI', 16, 'bold'), # 减小字体
bg="#ffffff",
fg="#2d3748")
title_label.pack()
# 输入区域
input_frame = tk.Frame(main_frame, bg="#ffffff")
input_frame.pack(fill=tk.BOTH, expand=True)
# 网站/应用名称 - 美观设计
tk.Label(input_frame,
text="网站/应用名称",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 3)) # 减少padding
website_var = tk.StringVar(value=entry['website'] if entry else "")
website_entry = tk.Entry(input_frame,
textvariable=website_var,
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
website_entry.pack(fill=tk.X, ipady=6, pady=(0, 10)) # 减少padding
# 用户名 - 美观设计
tk.Label(input_frame,
text="用户名",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 3)) # 减少padding
username_var = tk.StringVar(value=entry['username'] if entry else "")
username_entry = tk.Entry(input_frame,
textvariable=username_var,
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
username_entry.pack(fill=tk.X, ipady=6, pady=(0, 10)) # 减少padding
# 密码 - 美观设计
tk.Label(input_frame,
text="密码",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 3)) # 减少padding
password_var = tk.StringVar(value=entry['password'] if entry else "")
password_entry = tk.Entry(input_frame,
textvariable=password_var,
show="●",
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
password_entry.pack(fill=tk.X, ipady=6, pady=(0, 10)) # 减少padding
# TOTP密钥 - 美观设计
tk.Label(input_frame,
text="TOTP密钥(可选,Base32格式)",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 3)) # 减少padding
totp_var = tk.StringVar(value=entry['totp_secret'] if entry else "")
totp_entry = tk.Entry(input_frame,
textvariable=totp_var,
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
totp_entry.pack(fill=tk.X, ipady=6, pady=(0, 10)) # 减少padding
# 备注 - 美观设计
tk.Label(input_frame,
text="备注(可选)",
font=('Segoe UI', 10, 'bold'),
bg="#ffffff",
fg="#2d3748").pack(anchor=tk.W, pady=(0, 3)) # 减少padding
notes_var = tk.StringVar(value=entry['notes'] if entry else "")
notes_entry = tk.Entry(input_frame,
textvariable=notes_var,
font=('Segoe UI', 11),
bg="#f7fafc",
fg="#2d3748",
relief=tk.FLAT,
highlightthickness=2,
highlightbackground="#e2e8f0",
highlightcolor="#667eea")
notes_entry.pack(fill=tk.X, ipady=6, pady=(0, 15)) # 减少padding
# 按钮框架 - 美观设计
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(fill=tk.X, pady=15) # 增加padding
def save():
website = website_var.get()
username = username_var.get()
password = password_var.get()
totp = totp_var.get()
notes = notes_var.get()
if not website or not username or not password:
messagebox.showwarning("⚠️ 警告", "网站、用户名和密码不能为空!")
return
if entry:
# 更新
success = self.engine.update_password(
entry['id'], website, username, password, notes, totp
)
else:
# 添加
success = self.engine.add_password(
website, username, password, notes, totp
)
if success:
messagebox.showinfo("✅ 成功", "密码保存成功!")
dialog.destroy()
self.load_passwords()
else:
messagebox.showerror("❌ 错误", "密码保存失败!")
# 保存按钮 - 绿色大按钮
save_button = tk.Button(button_frame,
text="✅ 保存", # 添加图标
command=save,
font=('Segoe UI', 13, 'bold'), # 增大字体
bg="#48bb78",
fg="white",
activebackground="#38a169",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
height=2) # 设置按钮高度
save_button.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10), ipady=12)
# 取消按钮 - 灰色按钮
cancel_button = tk.Button(button_frame,
text="❌ 取消",
command=dialog.destroy,
font=('Segoe UI', 13, 'bold'), # 增大字体
bg="#718096",
fg="white",
activebackground="#4a5568",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2",
height=2) # 设置按钮高度
cancel_button.pack(side=tk.RIGHT, fill=tk.X, expand=True, ipady=12)
def generate_password(self):
"""生成密码"""
password = SecurityEngine.generate_password(
length=self.length_var.get(),
use_uppercase=self.uppercase_var.get(),
use_lowercase=self.lowercase_var.get(),
use_digits=self.digits_var.get(),
use_special=self.special_var.get(),
exclude_similar=self.exclude_similar_var.get()
)
self.generated_password_var.set(password)
# 更新强度评估
self.test_password_var.set(password)
self.update_strength()
def copy_generated_password(self):
"""复制生成的密码"""
password = self.generated_password_var.get()
if password:
SecurityEngine.copy_to_clipboard(password)
messagebox.showinfo("成功", "密码已复制到剪贴板!")
def update_strength(self, event=None):
"""更新密码强度显示"""
password = self.test_password_var.get()
score, level, color = SecurityEngine.calculate_password_strength(password)
# 更新进度条
self.strength_progress['value'] = score
# 更新强度标签
self.strength_label_var.set(f"密码强度: {level}")
# 提供建议
suggestions = []
if len(password) < 8:
suggestions.append("• 建议长度至少8位")
if not any(c.isupper() for c in password):
suggestions.append("• 建议包含大写字母")
if not any(c.islower() for c in password):
suggestions.append("• 建议包含小写字母")
if not any(c.isdigit() for c in password):
suggestions.append("• 建议包含数字")
if not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
suggestions.append("• 建议包含特殊字符")
if suggestions:
self.suggestion_var.set('\n'.join(suggestions))
else:
self.suggestion_var.set("✓ 密码强度良好")
def run(self):
"""运行应用"""
self.root.mainloop()
# ==================== 主函数 ====================
def main():
"""主函数"""
print("=" * 60)
print("本地化零信任密码管理与健康度评估系统")
print("完全使用Python标准库,无需安装任何依赖")
print("=" * 60)
print("\n正在启动系统...")
try:
app = PasswordManagerApp()
app.run()
except Exception as e:
print(f"\n错误:{e}")
print("\n本程序使用Python标准库,无需安装任何依赖")
sys.exit(1)
if __name__ == '__main__':
main()












浙公网安备 33010602011771号