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/22 14:14:32

图解minicom界面功能:Linux终端调试利器

图解minicom&#xff1a;为什么老派工具仍是嵌入式开发的“定海神针”&#xff1f;你有没有遇到过这样的场景——手里的开发板连不上网络&#xff0c;SSH登不进去&#xff0c;屏幕一片漆黑&#xff0c;唯一的希望就是那根不起眼的USB转TTL串口线&#xff1f;这时候&#xff0c;…

作者头像 李华
网站建设 2026/4/15 18:21:26

突破效率瓶颈:5大核心功能助你养成终身受益的好习惯

你是否曾经立下宏伟目标&#xff0c;却在几周后悄然放弃&#xff1f;明明知道好习惯的重要性&#xff0c;却总是无法坚持到底&#xff1f;别担心&#xff0c;这不仅仅是你的问题——90%的人都在习惯养成的道路上遭遇过滑铁卢。今天&#xff0c;让我们一起探索Loop Habit Tracke…

作者头像 李华
网站建设 2026/4/18 5:55:50

Cherry MX键帽终极指南:打造个性化机械键盘的完整解决方案

在机械键盘的世界里&#xff0c;键帽不仅是功能部件&#xff0c;更是个性表达的载体。Cherry MX键帽3D模型库为每一位键盘爱好者提供了从基础到高级的完整工具集&#xff0c;让个性化定制变得简单而高效。&#x1f3af; 【免费下载链接】cherry-mx-keycaps 3D models of Chery …

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

Audacity终极免费音频编辑指南:从零基础到专业制作

Audacity终极免费音频编辑指南&#xff1a;从零基础到专业制作 【免费下载链接】audacity Audio Editor 项目地址: https://gitcode.com/GitHub_Trending/au/audacity 想要轻松处理音频却担心操作复杂&#xff1f;Audacity作为一款完全免费的跨平台音频编辑软件&#x…

作者头像 李华
网站建设 2026/4/17 17:34:53

ServerPackCreator:告别手动配置,轻松生成Minecraft服务器包

ServerPackCreator&#xff1a;告别手动配置&#xff0c;轻松生成Minecraft服务器包 【免费下载链接】ServerPackCreator Create a server pack from a Minecraft Forge, NeoForge, Fabric, LegacyFabric or Quilt modpack! 项目地址: https://gitcode.com/gh_mirrors/se/Ser…

作者头像 李华
网站建设 2026/4/18 9:38:20

Chaplin视觉语音识别工具终极使用指南:让无声交流变得简单

Chaplin视觉语音识别工具终极使用指南&#xff1a;让无声交流变得简单 【免费下载链接】chaplin A real-time silent speech recognition tool. 项目地址: https://gitcode.com/gh_mirrors/chapl/chaplin 还在为嘈杂环境下无法语音输入而烦恼吗&#xff1f;&#x1f914…

作者头像 李华