news 2026/4/23 11:31:19

基于Java Swing的本地密码管理器(2)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Java Swing的本地密码管理器(2)

1、演示视频

基于Java Swing的本地密码管理器

2、项目截图

设计说明

3.1 整体架构设计

项目采用分层设计思想,分为界面层、业务逻辑层、数据存储层、加密算法层,各层职责清晰,低耦合高内聚:

  • 界面层(GUI):基于Swing框架实现,包含主界面、添加密码对话框、修改密码对话框,负责用户交互;
  • 业务逻辑层:包含密码生成、数据校验、事件处理等逻辑,衔接界面层和数据存储层;
  • 数据存储层:负责本地JSON文件的读写,实现密码条目的增删改查;
  • 加密算法层:封装AES加密解密逻辑,为密码存储提供安全保障。

3.2 类结构设计

类名所属层级核心职责
AESUtil加密算法层实现AES-128加密/解密,提供加密解密静态方法
PasswordEntry数据模型层密码条目实体类,封装网站、账号、加密密码属性
PasswordStorage数据存储层处理本地JSON文件读写,实现密码条目的增删改查
PasswordGenerator业务逻辑层生成随机强密码,确保密码包含多种字符类型
PasswordManagerGUI界面层主界面类,包含组件初始化、事件绑定、表格数据展示
AddPasswordDialog界面层添加密码对话框,处理密码添加的用户输入和提交
EditPasswordDialog界面层修改密码对话框,处理密码修改的用户输入和提交
JHintTextField界面层自定义带占位符的文本框,兼容JDK 8

3.3 数据存储设计

数据存储采用JSON格式,文件路径为System.getProperty("user.home") + "/passwords.json"(用户主目录),存储结构为密码条目数组,示例如下:

[ { "website": "淘宝", "account": "user123@taobao.com", "password": "加密后的密码字符串" }, { "website": "微信", "account": "13800138000", "password": "加密后的密码字符串" } ]

采用JSON格式的优势:可读性强、易于解析、跨平台兼容,借助Gson库可快速实现Java对象与JSON字符串的转换。

四、算法说明

4.1 AES对称加密算法

本项目采用AES-128加密算法(CBC模式,PKCS5Padding填充)对密码进行加密,核心参数:

  • 密钥(KEY):固定16位字符串(可自定义),示例:1234567890abcdef
  • 初始化向量(IV):固定16位字符串(可自定义),示例:abcdef1234567890
  • 字符编码:UTF-8;
  • 加密结果:Base64编码后的字符串,便于存储和传输。

加密流程:

  1. 将明文密码转换为UTF-8编码的字节数组;
  2. 通过密钥和IV初始化AES加密器(ENCRYPT_MODE);
  3. 对字节数组进行加密,得到加密后的字节数组;
  4. 将加密后的字节数组转换为Base64字符串,作为最终存储的密码。

解密流程:

  1. 将Base64编码的加密密码解码为字节数组;
  2. 通过密钥和IV初始化AES解密器(DECRYPT_MODE);
  3. 对字节数组进行解密,得到明文密码的字节数组;
  4. 将字节数组转换为UTF-8编码的字符串,得到原始密码。

4.2 随机强密码生成算法

强密码生成算法确保生成的密码包含大写字母、小写字母、数字、特殊符号四类字符,且长度可自定义(≥8位),核心步骤:

  1. 定义四类字符的字符集:大写字母(A-Z)、小写字母(a-z)、数字(0-9)、特殊符号(!@#$%^&*()_+-=[]{}|;:,.<>?);
  2. 确保密码至少包含每类字符各一个;
  3. 填充剩余长度的随机字符(从所有字符集中随机选取);
  4. 打乱密码字符顺序,避免前四位固定为四类字符的顺序;
  5. 返回最终生成的随机密码。

注意:密码长度建议不小于8位,长度越长,密码安全性越高;特殊符号的加入可大幅提升密码的抗破解能力。

五、测试说明

5.1 测试环境

测试项测试环境
JDK版本JDK 8(1.8.0_301)
操作系统Windows 10 / macOS 14 / Ubuntu 22.04
依赖库Gson 2.8.9(JSON解析)

5.2 功能测试用例

测试用例ID测试功能测试步骤预期结果测试结果
TC001添加密码1. 点击“添加密码”按钮;2. 输入网站“测试网站”、账号“test@163.com”、密码“123456”;3. 点击“保存”提示“添加成功”,表格中显示该条目,passwords.json文件新增该记录(密码为加密后字符串)通过
TC002查询密码1. 在搜索框输入“测试”;2. 点击“查询”按钮表格仅显示包含“测试”关键词的密码条目通过
TC003修改密码1. 选择“测试网站”条目;2. 点击“修改密码”;3. 输入新密码“654321”;4. 点击“保存修改”提示“修改成功”,表格中该条目密码更新为新的加密字符串,原密码可通过“显示原密码”查看通过
TC004删除密码1. 选择“测试网站”条目;2. 点击“删除密码”;3. 确认删除提示“删除成功”,表格中该条目消失,passwords.json文件中该记录被删除通过
TC005生成强密码1. 点击“生成强密码”;2. 输入长度“12”;3. 确认生成弹出窗口显示12位随机密码(包含大小写、数字、特殊符号),密码可复制到剪贴板通过
TC006加密解密验证1. 添加密码“123456”;2. 解密加密后的字符串解密结果与原始密码一致通过

5.3 边界测试

  • 密码长度测试:生成长度为8位、16位、32位的密码,验证生成结果符合规则;
  • 空输入测试:添加密码时网站/账号/密码为空,验证系统提示“不能为空”;
  • 关键词为空测试:查询框为空时,点击查询,显示所有密码条目;
  • 无效数字测试:生成密码时输入非数字长度(如“abc”),验证系统提示“请输入有效的数字”;
  • 短密码测试:生成密码时输入长度“7”,验证系统提示“密码长度不能小于8位”。

六、关键代码

6.1 AES加密解密工具类(AESUtil.java) import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * AES加密工具类(兼容JDK8,CBC模式,PKCS5Padding填充) */ public class AESUtil { // 加密密钥(建议替换为自己的密钥,长度必须是16位(AES-128)、24位(AES-192)或32位(AES-256)) private static final String KEY = "1234567890abcdef"; // 初始化向量(IV),长度必须是16位 private static final String IV = "abcdef1234567890"; /** * AES加密 * @param content 要加密的内容 * @return 加密后的Base64字符串 * @throws Exception 加密异常 */ public static String encrypt(String content) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES"); IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } /** * AES解密 * @param content 加密后的Base64字符串 * @return 解密后的原始字符串 * @throws Exception 解密异常 */ public static String decrypt(String content) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES"); IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); byte[] decoded = Base64.getDecoder().decode(content); byte[] decrypted = cipher.doFinal(decoded); return new String(decrypted, StandardCharsets.UTF_8); } } 6.2 强密码生成工具类(PasswordGenerator.java) import java.util.Random; /** * 随机强密码生成工具类 */ public class PasswordGenerator { // 密码包含的字符:大写字母、小写字母、数字、特殊符号 private static final String UPPER_CASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String LOWER_CASE = "abcdefghijklmnopqrstuvwxyz"; private static final String NUMBERS = "0123456789"; private static final String SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?"; private static final String ALL_CHARS = UPPER_CASE + LOWER_CASE + NUMBERS + SYMBOLS; private static final Random RANDOM = new Random(); /** * 生成随机强密码 * @param length 密码长度(建议至少8位) * @return 随机密码 */ public static String generateStrongPassword(int length) { if (length < 8) { throw new IllegalArgumentException("密码长度不能小于8位"); } StringBuilder password = new StringBuilder(); // 确保包含至少一种大写、小写、数字、特殊符号 password.append(UPPER_CASE.charAt(RANDOM.nextInt(UPPER_CASE.length()))); password.append(LOWER_CASE.charAt(RANDOM.nextInt(LOWER_CASE.length()))); password.append(NUMBERS.charAt(RANDOM.nextInt(NUMBERS.length()))); password.append(SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()))); // 填充剩余字符 for (int i = 4; i < length; i++) { password.append(ALL_CHARS.charAt(RANDOM.nextInt(ALL_CHARS.length()))); } // 打乱字符顺序(避免前四位固定为大写、小写、数字、符号) return shuffleString(password.toString()); } /** * 打乱字符串顺序 * @param str 原始字符串 * @return 打乱后的字符串 */ private static String shuffleString(String str) { char[] chars = str.toCharArray(); for (int i = chars.length - 1; i > 0; i--) { int j = RANDOM.nextInt(i + 1); char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } return new String(chars); } } 6.3 数据存储工具类(PasswordStorage.java) import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.*; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; /** * 密码数据存储工具类,处理本地JSON文件的读写 */ public class PasswordStorage { // 数据存储的本地文件路径(用户主目录下的passwords.json) private static final String STORAGE_FILE = System.getProperty("user.home") + File.separator + "passwords.json"; private static final Gson gson = new Gson(); private static final Type LIST_TYPE = new TypeToken>() {}.getType(); /** * 读取所有密码信息 * @return 密码列表 */ public static List loadPasswords() { File file = new File(STORAGE_FILE); if (!file.exists()) { return new ArrayList<>(); } try (Reader reader = new FileReader(file)) { return gson.fromJson(reader, LIST_TYPE); } catch (Exception e) { e.printStackTrace(); return new ArrayList<>(); } } /** * 保存密码列表到本地文件 * @param entries 密码列表 * @return 是否保存成功 */ public static boolean savePasswords(List entries) { try (Writer writer = new FileWriter(STORAGE_FILE)) { gson.toJson(entries, writer); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 添加密码条目 * @param entry 密码条目 * @return 是否添加成功 */ public static boolean addPassword(PasswordEntry entry) { List entries = loadPasswords(); entries.add(entry); return savePasswords(entries); } /** * 根据网站和账号删除密码条目 * @param website 网站 * @param account 账号 * @return 是否删除成功 */ public static boolean deletePassword(String website, String account) { List entries = loadPasswords(); boolean removed = entries.removeIf(e -> e.getWebsite().equals(website) && e.getAccount().equals(account)); if (removed) { return savePasswords(entries); } return false; } /** * 根据网站和账号修改密码 * @param website 网站 * @param account 账号 * @param newPassword 新密码(加密后的) * @return 是否修改成功 */ public static boolean updatePassword(String website, String account, String newPassword) { List entries = loadPasswords(); for (PasswordEntry entry : entries) { if (entry.getWebsite().equals(website) && entry.getAccount().equals(account)) { entry.setPassword(newPassword); return savePasswords(entries); } } return false; } /** * 查询密码条目(支持模糊查询网站或账号) * @param keyword 关键词 * @return 匹配的密码列表 */ public static List searchPasswords(String keyword) { List entries = loadPasswords(); List result = new ArrayList<>(); for (PasswordEntry entry : entries) { if (entry.getWebsite().contains(keyword) || entry.getAccount().contains(keyword)) { result.add(entry); } } return result; } } 6.4 自定义占位符文本框(JHintTextField.java) import javax.swing.*; import java.awt.*; /** * 自定义带占位符的文本框(兼容JDK8,修复尺寸和输入问题) */ public class JHintTextField extends JTextField { private String hint; // 占位符文字 // 构造方法1:仅指定占位符,使用默认列数20 public JHintTextField(String hint) { this(hint, 20); // 默认20列,确保有足够宽度 } // 构造方法2:指定占位符和列数(推荐使用) public JHintTextField(String hint, int columns) { super(columns); // 调用父类的列数构造方法,设置文本框列数 this.hint = hint; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 如果文本框为空,绘制占位符 if (getText().isEmpty() && hint != null) { Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.GRAY); // 占位符文字颜色 // 调整占位符的绘制位置,与文本框的默认文字对齐 int y = g.getFontMetrics().getAscent() + (getHeight() - g.getFontMetrics().getHeight()) / 2; g2.drawString(hint, getInsets().left, y); } } // 可选:重写首选尺寸,确保占位符文字能完整显示 @Override public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); if (hint != null) { FontMetrics fm = getFontMetrics(getFont()); int hintWidth = fm.stringWidth(hint) + getInsets().left + getInsets().right + 10; // 如果占位符宽度大于默认尺寸,使用占位符宽度 size.width = Math.max(size.width, hintWidth); } return size; } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 13:04:12

基于Java Swing的本地密码管理器(1)

1、项目介绍 本项目是一款基于Java语言开发的本地密码管理工具&#xff0c;兼容JDK 8及以上版本&#xff0c;采用Swing框架实现图形化用户界面&#xff08;GUI&#xff09;&#xff0c;无需额外依赖即可运行。 项目核心目标是解决用户本地密码存储的安全性问题&#xff0c;通…

作者头像 李华
网站建设 2026/4/19 2:49:06

Obsidian科研模板:如何打造个人知识管理系统的完整指南

Obsidian科研模板&#xff1a;如何打造个人知识管理系统的完整指南 【免费下载链接】obsidian_vault_template_for_researcher This is an vault template for researchers using obsidian. 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian_vault_template_for_resear…

作者头像 李华
网站建设 2026/4/21 20:19:30

PC通讯工具消息保护完全指南:5分钟掌握永久记录技巧

PC通讯工具消息保护完全指南&#xff1a;5分钟掌握永久记录技巧 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.com/…

作者头像 李华
网站建设 2026/4/20 22:06:11

UE4SS终极实战手册:从新手到专家的游戏修改进阶之路

你是否曾经遇到过这些问题&#xff1f;想要修改游戏却无从下手&#xff0c;面对复杂的游戏引擎感到困惑&#xff0c;或者想要深入了解游戏内部机制却缺乏合适工具&#xff1f;UE4SS正是为解决这些痛点而生&#xff0c;这个强大的虚幻引擎工具套件将为你打开游戏修改的全新世界。…

作者头像 李华
网站建设 2026/4/15 11:19:34

TouchGAL开源项目:构建专属Galgame社区的完整实践指南

TouchGAL开源项目&#xff1a;构建专属Galgame社区的完整实践指南 【免费下载链接】kun-touchgal-next TouchGAL是立足于分享快乐的一站式Galgame文化社区, 为Gal爱好者提供一片净土! 项目地址: https://gitcode.com/gh_mirrors/ku/kun-touchgal-next TouchGAL作为一款现…

作者头像 李华
网站建设 2026/4/20 5:34:42

如何在iOS设备上远程控制Android手机:Scrcpy-iOS完整指南

如何在iOS设备上远程控制Android手机&#xff1a;Scrcpy-iOS完整指南 【免费下载链接】scrcpy-ios Scrcpy-iOS.app is a remote control tool for Android Phones based on [https://github.com/Genymobile/scrcpy]. 项目地址: https://gitcode.com/gh_mirrors/sc/scrcpy-ios…

作者头像 李华