目录
- 效果
- 项目结构
- 功能
- 1、登录
- 2、加密
- 3、解密
- 4、列表
- 代码
- 1、先准备好两种加密方式的工具类
- SHAUtil
- AESUtil
- 2、登录窗口
- 3、主页窗口(加密和解密面板)
- 4、主页窗口(列表面板)
- 5、主程序(main)
- 最后
通过SHA和AES加密方式,用Swing写的窗口界面
主要实现了登录、加密、解密、列表展示。其中密码加密后密文是以txt文件的形式保存到本地的。
我们来先看看效果。
效果
项目结构
功能
看完了效果和结构图,我们再来说下功能。
1、登录
在第一次使用这个程序时(通过有没有 密码管理器.txt 文件来判断是不是第一次使用),要先设置登录密码,以后用这个程序都是要用这个密码登录,并且加密解密也是用这个密码作为密钥的。
登录密码是用sha256进行加密(不可逆),密文保存到了 密码管理器.txt,这个文件设置了只读和隐藏。后续通过输入的密码用同样方式加密得到密文和这个txt的密文进行验证,通过了才能进入到主页,没通过直接结束程序。
2、加密
加密是会用到登录密码的,以登录密码作为密钥加密。得到的密文保存到 加密密码.txt。如果程序/网站名在txt已经存在了,则会把原密文替换成新的密文(修改密码)。比如微信,一开始的密码是123456,后面又输入了微信和新的密码点击加密,在txt中原本微信对应的密文就会变成新密码的密文。
3、解密
解密同样会用到登录密码。输入密文点击解密时,要再次验证登录密码,这是防止你自己登录进来后,程序还在运行,别人直接拿你的密文去解密。 如果密码错了也是直接退出程序,正确则进行解密。
解密用登录密码作为密钥,这是防止别人拿到了我的密文,ta去使用这个程序,设置的登录密码也是ta自己的(ta不知道我的登录密码),然后输入我的密文,点击解密,输入ta的登录密码后校验通过,导致我的密文被解密出来了。 用密码作为密钥的话,只要不知道我的登录密码,即使ta能用这个程序,知道我的密文,也不可能解密出来。 是不是这么个道理😎
4、列表
这个只是展示 加密密码.txt 的内容的。本来一开始是想除了展示,还能直接在这个列表进行解密(过程和上面那个一样)和修改密码。后面发现JTable这个组件太难搞了,光是一个展示页面,要让它在tab面板里面显示出来、设置表格样式这些就花了我不少时间(对swing真不会用),要让它每一行显示两个按钮,并且是可以点击的按钮,不会搞,想想就算了(不知道以后有没有精力加上去)😢,还是只做展示用吧,要修改/解密去加密、解密面板操作吧。
代码
1、先准备好两种加密方式的工具类
SHAUtil
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
/**
* 密码盐值加密工具类
*/
public class SHAUtil {
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
/**
* 盐的长度
*/
public static final int SALT_BYTE_SIZE = 16;
/**
* 生成密文的长度
*/
public static final int HASH_BIT_SIZE = 256;
/**
* 迭代次数
*/
public static final int PBKDF2_ITERATIONS = 1000;
/**
* 验证输入的password是否正确
* @param attemptedPassword 待验证的password
* @param encryptedPassword 密文
* @param salt 盐值
*/
public static boolean authenticate(String attemptedPassword, String encryptedPassword, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 用同样的盐值对用户输入的password进行加密
String encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt);
// 把加密后的密文和原密文进行比較,同样则验证成功。否则失败
return encryptedAttemptedPassword.equals(encryptedPassword);
}
/**
* 生成密文
* @param password 明文password
* @param salt 盐值
*/
public static String getEncryptedPassword(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec spec = new PBEKeySpec(password.toCharArray(), fromHex(salt), PBKDF2_ITERATIONS, HASH_BIT_SIZE);
SecretKeyFactory f = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return toHex(f.generateSecret(spec).getEncoded());
}
/**
* 通过提供加密的强随机数生成器 生成盐
*/
public static String generateSalt() throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[SALT_BYTE_SIZE];
random.nextBytes(salt);
return toHex(salt);
}
/**
* 十六进制字符串转二进制字符串
* @param hex the hex string
*/
private static byte[] fromHex(String hex) {
byte[] binary = new byte[hex.length() / 2];
for (int i = 0; i < binary.length; i++) {
binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return binary;
}
/**
* 二进制字符串转十六进制字符串
* @param array the byte array to convert
*/
private static String toHex(byte[] array) {
BigInteger bi = new BigInteger(1, array);
String hex = bi.toString(16);
int paddingLength = (array.length * 2) - hex.length();
if (paddingLength > 0) return String.format("%0" + paddingLength + "d", 0) + hex;
else return hex;
}
}
AESUtil
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.io.*;
import java.util.Random;
/**
* AES加密、解密工具类
*/
public class AESUtil {
/*
* 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
* key是aes的一个加密源,也就是通过该key加密后,必须通过该key就进行解密;对数据不是特别敏感的,直接用静态的key即可;
* 有些场景下数据异常敏感,那么这个key就要动态生成,结合RSA进行加密,,就能做到每次请求,key都不一样,安全程度大大加强;
* 当然key可以为32,但是要下载两个jdk的jar包
*/
public static String 默认密钥 = "xcwssythjqruijkg";
public static String 密钥 = 默认密钥;
/**
* 偏移量,随便给一个就可以了,不给也行
*/
public static String 默认偏移量 = "xcwssythjqruijkg";
public static String 偏移量 = 默认偏移量;
/**
* 分隔符
*/
public static final String separate = "ZmhzeGZsd2I=";
public static String 登录密码 = "";
public static String 密文 = "";
/**
* 加密
* @param str 要加密的字符串
* @param flag 是否使用默认密钥和偏移量进行加密
*/
public static String encrypt(String str,boolean flag) {
int length = 登录密码.length();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
if (!flag){
密钥 = 登录密码 + generateRandom(16-length); // 随机生成密钥
偏移量 = 登录密码 + generateRandom(16-length); // 获取偏移量
}else {
密钥 = 登录密码 + 默认密钥.substring(0,16-length);
偏移量 = 登录密码 + 默认偏移量.substring(0,16-length);
}
//System.out.println("加密密钥和偏移量:"+密钥+"\t"+偏移量);
SecretKeySpec skeySpec = new SecretKeySpec(密钥.getBytes(), "AES");
IvParameterSpec ivp = new IvParameterSpec(偏移量.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivp);
byte[] encrypted = cipher.doFinal(str.getBytes("utf-8"));
return new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 解密
* @param str 要解密的字符串
* @param raw 密钥
* @param ivStr 偏移量
*/
public static String decrypt(String str,String raw,String ivStr){
try {
SecretKeySpec skeySpec = new SecretKeySpec(raw.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(str);// 先用base64解密
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception ex) {
return null;
}
}
/**
* 写入到txt文件
* @param path 文件路径
* @param start 内容开头
* @param content 内容
*/
public static void 写入内容(String path,String start,String content) {
BufferedWriter out = null;
BufferedReader reader = null;
StringBuilder sb = new StringBuilder();
try {
File file = new File(path);
if (!file.exists()){
file.createNewFile();
}
String str = "";
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
while((str = reader.readLine())!=null){
if (isNotBlank(start) && str.startsWith(start)){ // 替换旧内容
sb.append(content).append("\n");
}else {
sb.append(str).append("\n");
}
}
str = sb.toString();
if (!str.contains(content)){ // 追加新内容
sb.append(content).append("\n");
}
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
out.write(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String 读取登录密码(String path) {
BufferedReader reader = null;
String str = "";
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
while((str = reader.readLine())!=null){
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
密文 = str;
return str;
}
/**
* 根据指定长度随机生成字符串
*/
public static String generateRandom(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ++i) {
int number = random.nextInt(str.length());
sb.append(str.charAt(number));
}
return sb.toString();
}
public static boolean isNotBlank(String str){
if (str == null || str.length() == 0){
return false;
}
return true;
}
public static boolean isNull(String str){
if (str == null || str.length() == 0){
return true;
}
return false;
}
/**
* 提示框
*/
public static void tips(String str){
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 16)));
JOptionPane.showMessageDialog(null, str);
}
}
2、登录窗口
import com.util.AESUtil;
import com.util.SHAUtil;
import com.主程序;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
public class 登录 {
public JFrame frame;
public JPanel panel;
private JLabel label0;
private JButton 确定Button;
private JPasswordField 请输入登录密码PasswordField;
/**
* 登录采用SHA256盐值加密
* @param 调用位置 用于区分是在哪个地方调用显示这个窗口的:1 主程序 2 主页
*/
public 登录(int 调用位置) {
String path = System.getProperty("user.dir")+"/密码管理器.txt";
File file = new File(path);
if (!file.exists()) {
label0.setText("第一次使用请先设置登录密码");
}
确定Button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
String next = new String(请输入登录密码PasswordField.getPassword());
if (!AESUtil.isNotBlank(next)){
AESUtil.tips("请先输入登录密码!");
}else {
if (!file.exists()){ // 如果路径不存在这个文件,要求用户设置使用该应用程序的密码
String salt = SHAUtil.generateSalt(); // 生成盐值
String password = SHAUtil.getEncryptedPassword(next, salt); // 得到密文
password = password + AESUtil.separate + salt;
AESUtil.写入内容(path,null,password);
AESUtil.tips("登录密码设置成功!");
file.setReadOnly(); // 只读
Runtime.getRuntime().exec("attrib +H \"" + file.getAbsolutePath() + "\""); // 隐藏
AESUtil.登录密码 = next;
AESUtil.密文 = password;
}else {
String 密文 = AESUtil.读取登录密码(path);
String[] split = 密文.split(AESUtil.separate);
boolean authenticate = SHAUtil.authenticate(next,split[0], split[1]);
if (!authenticate){
AESUtil.tips("登录密码错误!无法使用本程序!");
System.exit(0);
}
AESUtil.登录密码 = next;
if (调用位置 == 1){ // 在主程序登录时,给个提示
AESUtil.tips("登录成功!");
}
}
frame.setVisible(false); // 隐藏当前登录窗口
if (调用位置 == 1){
主程序.主页Form(); // 加载主页窗口
}
}
}catch (Exception ex){
AESUtil.tips("登录密码错误!无法使用本程序!");
System.exit(0);
}
}
});
}
}
窗口是以xml形式保存的
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.form.登录">
<grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="8" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="615" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="19752" class="javax.swing.JLabel" binding="label0">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="5" use-parent-layout="false"/>
</constraints>
<properties>
<font size="28"/>
<foreground color="-4258806"/>
<labelFor value="d1622"/>
<text value="请输入登录密码"/>
</properties>
</component>
<vspacer id="517b1">
<constraints>
<grid row="3" column="1" row-span="5" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="5" use-parent-layout="false"/>
</constraints>
</vspacer>
<hspacer id="7b473">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="50"/>
</grid>
</constraints>
</hspacer>
<hspacer id="3c260">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<hspacer id="47b1b">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="30"/>
</grid>
</constraints>
</hspacer>
<component id="5547" class="javax.swing.JButton" binding="确定Button" default-binding="true">
<constraints>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="0" indent="5" use-parent-layout="false">
<preferred-size width="200" height="50"/>
</grid>
</constraints>
<properties>
<font size="24"/>
<text value="确定"/>
</properties>
</component>
<hspacer id="462e7">
<constraints>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<component id="d1622" class="javax.swing.JPasswordField" binding="请输入登录密码PasswordField" default-binding="true">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="5" use-parent-layout="false">
<preferred-size width="500" height="40"/>
</grid>
</constraints>
<properties>
<font size="18"/>
</properties>
</component>
</children>
</grid>
</form>
3、主页窗口(加密和解密面板)
import com.util.AESUtil;
import com.主程序;
import javax.swing.*;
import java.awt.event.*;
public class 主页 {
public JFrame frame;
public JPanel panel;
public JTabbedPane tabPane;
private JPanel 加密tab;
private JPanel 解密tab;
public JPanel 列表tab;
private JLabel label1;
private JTextField 输入应用的网站或软件TextField;
private JLabel label2;
private JLabel label4;
private JTextArea 加密后TextArea;
private JButton 加密Button;
private JPasswordField 输入密码PasswordField;
private JLabel label3;
private JPasswordField 确认密码PasswordField;
private JLabel label5;
private JTextArea 输入需要解密的密文TextArea;
private JButton 解密Button;
private JTextField 解密后TextField;
private JLabel label6;
public 主页() {
确认密码PasswordField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
super.focusLost(e);
String 密码 = new String(输入密码PasswordField.getPassword());
String 确认密码 = new String(确认密码PasswordField.getPassword());
if(!密码.equals(确认密码)){
AESUtil.tips("两次输入的密码不一致!");
}
}
});
加密Button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String 应用 = 输入应用的网站或软件TextField.getText();
String 密码 = new String(输入密码PasswordField.getPassword());
String 确认密码 = new String(确认密码PasswordField.getPassword());
if (AESUtil.isNull(应用) || AESUtil.isNull(密码) || AESUtil.isNull(确认密码)){
AESUtil.tips("上面三个输入框为必填项!");
return;
}
if(!密码.equals(确认密码)){
AESUtil.tips("两次输入的密码不一致!");
return;
}
String 保存位置 = System.getProperty("user.dir")+"/加密密码.txt";
String 密码加密 = AESUtil.encrypt(密码,false); // 先使用随机密钥加密密码
密码加密 = AESUtil.密钥 + AESUtil.separate + AESUtil.偏移量 + AESUtil.separate + 密码加密;
// 再将 密钥、偏移量、加密的密码拼接在一起,通过默认密钥再进行一次加密,得到最终密文
String 最终密文 = AESUtil.encrypt(密码加密,true).replaceAll("\r\n","");
加密后TextArea.setText(最终密文);
String 输出内容 = 应用 + ":" + 最终密文;
AESUtil.写入内容(保存位置,应用,输出内容);
}
});
解密Button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
主程序.登录Form(2); // 点击界面按钮,以模态框的形式显示登录窗口,校验登录密码。
String text = 输入需要解密的密文TextArea.getText();
if (AESUtil.isNotBlank(text)){
String 密钥 = AESUtil.登录密码 + AESUtil.默认密钥.substring(0,16 - AESUtil.登录密码.length());
String 偏移量 = AESUtil.登录密码 + AESUtil.默认偏移量.substring(0,16 - AESUtil.登录密码.length());
//System.out.println("解密密钥和偏移量:"+密钥+"\t"+偏移量);
String 密文 = AESUtil.decrypt(text,密钥,偏移量);
if (AESUtil.isNull(密文)){
AESUtil.tips("解密失败,请检查密文是否正确!");
return;
}
String[] split = 密文.split(AESUtil.separate);
String 密码 = AESUtil.decrypt(split[2],split[0],split[1]);
if (AESUtil.isNull(密文)){
AESUtil.tips("解密失败,请检查密文是否正确!");
return;
}
解密后TextField.setText(密码);
}else {
AESUtil.tips("请输入需要解密的密文!");
}
}
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.form.主页">
<grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="2529" height="817"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<tabbedpane id="dbc08" binding="tabPane">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="3" use-parent-layout="false">
<preferred-size width="1000" height="600"/>
</grid>
</constraints>
<properties>
<font name="Consolas" size="24"/>
</properties>
<border type="none"/>
<children>
<grid id="e28e7" binding="加密tab" layout-manager="GridLayoutManager" row-count="12" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<tabbedpane title="加密"/>
</constraints>
<properties>
<font/>
</properties>
<border type="none"/>
<children>
<hspacer id="621b5">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<component id="6d000" class="javax.swing.JLabel" binding="label1">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="ec6a5"/>
<text value="输入应用的网站或软件"/>
</properties>
</component>
<component id="ec6a5" class="javax.swing.JTextField" binding="输入应用的网站或软件TextField" default-binding="true">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="500" height="30"/>
</grid>
</constraints>
<properties>
<columns value="0"/>
<font size="18"/>
</properties>
</component>
<component id="efd75" class="javax.swing.JLabel" binding="label2">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="8bad4"/>
<text value="输入密码"/>
</properties>
</component>
<component id="8bad4" class="javax.swing.JPasswordField" binding="输入密码PasswordField" default-binding="true">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="500" height="30"/>
</grid>
</constraints>
<properties>
<font size="18"/>
</properties>
</component>
<component id="9eb3d" class="javax.swing.JLabel" binding="label3">
<constraints>
<grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="accd4"/>
<text value="确认密码"/>
</properties>
</component>
<component id="accd4" class="javax.swing.JPasswordField" binding="确认密码PasswordField" default-binding="true">
<constraints>
<grid row="6" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="500" height="30"/>
</grid>
</constraints>
<properties>
<font size="18"/>
</properties>
</component>
<component id="70f6c" class="javax.swing.JButton" binding="加密Button" default-binding="true">
<constraints>
<grid row="7" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<background color="-3148033"/>
<font name="Consolas" size="24" style="1"/>
<foreground color="-14065484"/>
<text value="加密"/>
</properties>
</component>
<component id="48e2f" class="javax.swing.JLabel" binding="label4">
<constraints>
<grid row="8" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="20165"/>
<text value="加密后"/>
</properties>
</component>
<component id="20165" class="javax.swing.JTextArea" binding="加密后TextArea" default-binding="true">
<constraints>
<grid row="9" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="500" height="150"/>
</grid>
</constraints>
<properties>
<enabled value="true"/>
<font size="18"/>
<lineWrap value="true"/>
<wrapStyleWord value="true"/>
</properties>
</component>
<hspacer id="fe821">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="3" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="d1224">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="3" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="2dcc">
<constraints>
<grid row="10" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
</children>
</grid>
<grid id="9db33" binding="解密tab" layout-manager="GridLayoutManager" row-count="11" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<tabbedpane title="解密"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<hspacer id="ed522">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<component id="b7c47" class="javax.swing.JLabel" binding="label5">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="efa68"/>
<text value="输入需要解密的密文"/>
</properties>
</component>
<component id="efa68" class="javax.swing.JTextArea" binding="输入需要解密的密文TextArea" default-binding="true">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="500" height="150"/>
</grid>
</constraints>
<properties>
<font size="18"/>
<lineWrap value="true"/>
<wrapStyleWord value="true"/>
</properties>
</component>
<component id="1cf30" class="javax.swing.JButton" binding="解密Button" default-binding="true">
<constraints>
<grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<background color="-3148033"/>
<font name="Consolas" size="24" style="1"/>
<foreground color="-14065484"/>
<horizontalTextPosition value="0"/>
<text value="解密"/>
</properties>
</component>
<component id="145c5" class="javax.swing.JLabel" binding="label6">
<constraints>
<grid row="7" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<background color="-14605013"/>
<font name="Consolas" size="20"/>
<foreground color="-1942775"/>
<labelFor value="7d1e"/>
<text value="解密后"/>
</properties>
</component>
<component id="7d1e" class="javax.swing.JTextField" binding="解密后TextField" default-binding="true">
<constraints>
<grid row="9" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="500" height="30"/>
</grid>
</constraints>
<properties>
<enabled value="true"/>
<font size="18"/>
</properties>
</component>
<hspacer id="f8a36">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="3" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="efa45">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="3" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="2c93e">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<hspacer id="41ab9">
<constraints>
<grid row="6" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<hspacer id="a62e9">
<constraints>
<grid row="8" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="10"/>
</grid>
</constraints>
</hspacer>
<hspacer id="531f5">
<constraints>
<grid row="10" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="20"/>
</grid>
</constraints>
</hspacer>
<hspacer id="43303">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="10"/>
</grid>
</constraints>
</hspacer>
</children>
</grid>
</children>
</tabbedpane>
<vspacer id="f6bfb">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="3" use-parent-layout="false"/>
</constraints>
</vspacer>
<hspacer id="c482c">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="30"/>
</grid>
</constraints>
</hspacer>
<hspacer id="78883">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="-1" height="30"/>
</grid>
</constraints>
</hspacer>
</children>
</grid>
</form>
4、主页窗口(列表面板)
列表面板是单独拿出来,加载主页窗口时,手动添加一个tab,把列表面板放到这个tab中显示的。
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.table.*;
import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class 列表 extends JPanel {
private DefaultTableModel tableModel;
public JTable table;
private TableColumn column;
private int DEFAULE_WIDTH = 1000;
private int DEFAULE_HEIGH = 560;
private String[] 表头 = { "程序/网站", "密文"};
private String[][] 数据 = new String[0][0]; // 初始化一开始先设置长度为0,不然即使没数据,表格也会显示一行空行
public 列表() {
setBorder(new EmptyBorder(5, 5, 5, 5));
setLayout(new BorderLayout());
setPreferredSize(new Dimension(DEFAULE_WIDTH, DEFAULE_HEIGH));
createTable();
}
public void createTable(){
int[] width = {100, 700}; // 列宽
读取内容(); // 填充表头和数据行
tableModel = new DefaultTableModel(数据, 表头){ // 用双数组创建DefaultTableModel对象
@Override
public boolean isCellEditable(int row, int column) {
if(column == 1){
return true;
}else{
return false; //表格不允许被编辑
}
}
};
table = new JTable(tableModel); // 创建表格组件
// 设置表头样式
JTableHeader head = table.getTableHeader(); // 创建表格标题对象
head.setPreferredSize(new Dimension(head.getWidth(), 50));// 设置表头大小
head.setFont(new Font("宋体", Font.BOLD, 20));// 设置表格字体
head.setReorderingAllowed(false); // 设置表头不可拖动
// 设置表格样式
table.setRowHeight(40);// 设置表格行高
table.setFont(new Font("宋体",0,14));
table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);// 以下设置表格列宽
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
for (int i = 0; i < 2; i++) {
column = table.getColumnModel().getColumn(i);
column.setPreferredWidth(width[i]);
}
// 设置表格间隔色(表格单元格以JLabel的形式显示)
DefaultTableCellRenderer ter = new DefaultTableCellRenderer() {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (row % 2 == 0) setBackground(Color.pink);
else if (row % 2 == 1) setBackground(Color.white);
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
};
ter.setHorizontalAlignment(JLabel.CENTER);// 设置表格内容居中显示
table.getColumn(表头[0]).setCellRenderer(ter);
table.getColumn(表头[1]).setCellRenderer(new TableViewRenderer());
// 创建滚动条组件,默认滚动条始终出现,初始化列表组件
JScrollPane scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
add(scrollPane, BorderLayout.CENTER);
}
private void 读取内容() {
String path = System.getProperty("user.dir")+"/加密密码.txt";
File file = new File(path);
if (!file.exists()){
return;
}
BufferedReader reader = null;
String str = "";
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
List<String> list = new ArrayList<>();
while((str = reader.readLine())!=null){
list.add(str);
}
数据 = new String[list.size()][];
for (int i = 0; i < list.size(); i++) {
String[] split = list.get(i).toString().split(":");
String[] arr = {split[0], split[1]};
数据[i] = arr;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void 刷新(){
读取内容(); // 先重新读取一遍txt的内容,得到最新的数据
int rowCount = tableModel.getRowCount(); // 获得未刷新前表格的行数(没数据时默认会有一行)
int length = 数据.length; // 得到最新有多少条数据
for (int i = 0; i < 数据.length; i++) {
String[] obj = 数据[i];
// txt的行数大于表格的行数,要将txt多出来的数据添加到表格中,只将新数据添加到表格中
if (i >= rowCount){
tableModel.addRow(obj);
}
String col0 = (String) tableModel.getValueAt(i, 0);
String col1 = (String) tableModel.getValueAt(i, 1);
// 这里是假如在加密中改了某一项的密码,密文也就变了,所以把某项对应的新密文更新到表格中
if (obj[0].equals(col0) && !obj[1].equals(col1)){
tableModel.setValueAt(obj[1],i,1);
}
}
tableModel.setRowCount(length); // 更新表格的行数
}
}
这个是用于设置密文那一列的单元格以JTextArea(多行文本)的形式显示,可以自动换行。
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
public class TableViewRenderer extends JTextArea implements TableCellRenderer {
public TableViewRenderer() {
super();
//将表格设为自动换行
setLineWrap(true); //利用JTextArea的自动换行方法
setFont(new Font("Consolas",0,14));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
setText(value == null ? "" : value.toString()); //利用JTextArea的setText设置文本方法
// 设置隔行色
if (row % 2 == 0) setBackground(Color.pink);
else if (row % 2 == 1) setBackground(Color.white);
if (isSelected){ // 如果当前单元格是选中状态,变更背景色
setBackground(table.getSelectionBackground());
}
return this;
}
}
5、主程序(main)
import com.form.主页;
import com.form.列表;
import com.form.登录;
import javax.swing.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class 主程序 {
public static void main(String[] args) {
登录Form(1);
}
/**
* 加载登录窗口
* @param 调用位置 用于区分是在哪个地方调用显示这个窗口的:1 主程序 2 主页
*/
public static void 登录Form(int 调用位置){
登录 登录 = new 登录(调用位置);
登录.frame = new JFrame("登录");
if (调用位置 == 2){
// 这里使用模态窗口,置顶,且只能操作这个窗口
JDialog dialog = new JDialog(登录.frame,"登录",true);
dialog.setSize(600,500);
dialog.setLocationRelativeTo(null);
dialog.setContentPane(登录.panel);
dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
dialog.pack();
dialog.setVisible(true);
dialog.setResizable(false);
}else {
登录.frame.setSize(600,500);
登录.frame.setLocationRelativeTo(null);
登录.frame.setContentPane(登录.panel);
登录.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
登录.frame.pack();
登录.frame.setVisible(true);
登录.frame.setResizable(false);
}
}
/**
* 加载主页窗口
*/
public static void 主页Form(){
主页 主页 = new 主页();
主页.frame = new JFrame("主页");
主页.frame.setSize(1000,600);
主页.frame.setLocationRelativeTo(null);
主页.frame.setContentPane(主页.panel);
主页.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
主页.frame.pack();
主页.frame.setVisible(true);
主页.frame.setResizable(false);
// 手动添加列表tab到tabPane中
列表 table = new 列表();
主页.列表tab = new JPanel();
主页.列表tab.add(new JScrollPane(table));
主页.tabPane.add("列表",主页.列表tab);
主页.tabPane.add(主页.列表tab,"列表",2);
// 每次选中列表tab时,刷新它表格里面的数据
主页.列表tab.addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
super.componentShown(e);
table.刷新();
}
});
}
}
最后
上面是全部代码了,觉得复制麻烦的同学,可以去 这里GitCode 直接下载,我把打包的jar和exe程序也放到这里了,有兴趣的可以下载来看看😁(这是2023年第一篇文章,求点赞求评论鸭,你的点赞和评论就是我前进的动力~🥰)
哦,对了,关于用到的exe4j打包工具(jar转exe),直接去 这里 下载吧!