如果要实现存储用户的秘钥,那就得把用户的登录注册界面分开写,在注册时进行账户密码秘钥的存储,在登录时仅仅是判断账号密码,在下载文件时判断秘钥,此秘钥同时进行文件加密的操作,在文件上传时也要能读取。
一旦把注册登录界面分开写,那么就要对动作监听器重新对应,原先的同一界面上的“登录”“注册”按钮,变成“登录”“新用户注册”,而点击新用户注册时跳转另一个注册页面
——>
(点击新用户注册)
同时关闭登录页面,注册完成再跳转会登录页面
之后,我对动作监听器里面的“注册判断”,“写入”方法进行调整
//注册判断
public void registerUser() {
String userid = userID.getText();
String password = new String(userPw.getPassword());// 获取密码字段的文本
String key = new String(userKey.getText());
if (userid == null || userid.trim().isEmpty()) {
System.out.println("账号不能为空");
return; // 直接返回,不执行注册操作
}
if (password == null || password.trim().isEmpty()) {
System.out.println("密码不能为空");
return; // 直接返回,不执行注册操作
}
if (key == null || userid.trim().isEmpty()) {
System.out.println("秘钥不能为空");
return; // 直接返回,不执行注册操作
}
if (isUserNameTaken(userid)) {
System.out.println("用户名已注册,请使用其他用户名");
} else {
System.out.println("注册成功");
writeUserInfo(userid, password, key);
}
}
//账号密码秘钥写入txt
private void writeUserInfo(String userid, String password, String key) {
try (FileWriter writer = new FileWriter("data\\userinfo.txt", true)) {
writer.write(userid + ":" + password + ":" + key + "\n");
} catch (IOException e) {
System.out.println("出现IO错误");
e.printStackTrace();//打印异常堆栈跟踪到标准错误流
}
}
效果如图
因为调用秘钥情况太多,我就将查找用户秘钥单独写了方法
//查找对应秘钥
public String getUserKey() {
String userid =userID.getText();
try {
BufferedReader reader = new BufferedReader((new FileReader("data\\userinfo.txt")));
//为了每一行的进行读取
String line;//用于存储每一行的内容
while ((line = reader.readLine()) != null) {//一行一行的从reader中读取,进行while循环
String[] parts = line.split(":");//split方法以“:”为分割,将每一行的内容存入数组
if (parts[0].equals(userid)) {
newKey= parts[2];
}
}
} catch (IOException e) {
System.out.println("读取文件时发生错误。");
e.printStackTrace();
}
return newKey;
}
和检索密码的逻辑相同,只是位置从part[1],变成part[2]。
当让,秘钥是服务于加密的,这里采用异或加密,加密的秘钥和解密的秘钥是同一个,加解密的方法是互补的,也就是只用一个方法就能写出文件加密解密,用一个布尔值区别加密解密(最后好像没用上)。
import java.io.*;
public class XorEncryption {
/**
* 对文件进行异或加密。
*
* @param inputFile 要加密的文件路径
* @param outputFile 加密后输出的文件路径
* @param key 密钥,必须是字节数组
* @throws IOException 如果读写文件时发生错误
*/
public static void encryptFile(String inputFile, String outputFile, byte[] key) throws IOException {
processFile(inputFile, outputFile, key, true);
}
/**
* 对文件进行异或解密。
*
* @param inputFile 要解密的文件路径
* @param outputFile 解密后输出的文件路径
* @param key 密钥,必须是字节数组
* @throws IOException 如果读写文件时发生错误
*/
public static void decryptFile(String inputFile, String outputFile, byte[] key) throws IOException {
processFile(inputFile, outputFile, key, false);
}
/**
* 对文件进行异或处理,可以是加密或解密。
*
* @param inputFile 要处理的文件路径
* @param outputFile 处理后输出的文件路径
* @param key 密钥,必须是字节数组
* @param isEncrypting 布尔值,true表示加密,false表示解密
* @throws IOException 如果读写文件时发生错误
*/
private static void processFile(String inputFile, String outputFile, byte[] key, boolean isEncrypting) throws IOException {
int keyLength = key.length;
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
for (int i = 0; i < bytesRead; i++) {
buffer[i] = (byte) (buffer[i] ^ key[i % keyLength]);
}
bos.write(buffer, 0, bytesRead);
}
}
}
}
接下来是文件的上传和下载
上次已经写到了打开用户界面时同时加载用户自己的文件夹,把文件遍历在窗体上面,同时加上复选框
这里我把上传的方案暂定为打开文件选择器,在硬盘中选择文件进行上传,目前有个缺点就是一次只能上传一个文件。
文件加密后要存入自己的文件夹,就涉及到用户名的传送,所以我写了两个构造器
public void jumpID(String userID){
ID=userID;
System.out.println("传递的账号ID:"+ID);
}
public void jumpPW(String userPW){
PW=userPW;
System.out.println("传递的账号PW:"+PW);
}
这个文件选择后,传入加密,秘钥为用户注册时设置的,加密后传出到用户自己的文件夹中。
upbtn.addActionListener(e -> {
uploadAndEncryptFile(ID);
});
// 上传文件并加密
public void uploadAndEncryptFile(String userID) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("选择要上传的文件");
int result = fileChooser.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
String filePath = selectedFile.getAbsolutePath();
String encryptedFilePath = "data/" + userID + "-Dir/" + selectedFile.getName();
try {
// 获取用户注册时的秘钥
String userKey = al.getUserKey();
byte[] key = userKey.getBytes();
// 加密文件
xor.encryptFile(filePath, encryptedFilePath, key);
System.out.println("文件上传并加密成功");
} catch (IOException e) {
System.out.println("文件加密或写入时发生错误");
e.printStackTrace();
}
}
}
然后就出现了列表不显示刚刚传入的文件的问题,这就涉及到一个页面刷新的问题
最开始,我认为刷新页面只要关闭再打开页面就行,而这样写过之后发现,文件列表依然不存在传入的文件,分析原因大概是文件的链表没有重新载入界面,一次偶然点击发现,关闭用户页面,重新点击登录按钮也可以刷新页面,那就按这个思路,写了一个刷新按钮
// 给刷新按钮添加动作监听器
flushed.addActionListener(e -> {
closeUserInfoUI();
openLoginUI();
userInfoUI(ID, PW);
closeLoginUI();
});
接下来是下载按钮
用户对文件进行复选操作后,点击下载按钮,跳出“请输入秘钥”界面,输入对应秘钥后,进行判断,如果秘钥没问题,则进行解密并下载操作
解密还是调用XOR异或加密类中的方法,传出文件
下载操作呢,我还是使用了一个文件选择器,用来选择下载的位置,同时,文件前缀加上“out-”,标识已经解密
//给下载按钮添加动作监听器
downbtn.addActionListener(e -> {
String userKey = al.getUserKey();
String pwd = JOptionPane.showInputDialog("请输入秘钥:");
if (pwd.equals(userKey)) {//从文件夹中找到用户存储的秘钥,进行二次判断
for (int i = 0; i < listModel.getSize(); i++) {
JCheckBox jCheckBox = listModel.get(i);
if (jCheckBox.isSelected()) {
File selectedFile = new File(filePath, jCheckBox.getText());
// 打开文件选择对话框,让用户选择保存位置
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("选择保存文件的位置");
fileChooser.setSelectedFile(new File("out-" + selectedFile.getName())); // 设置默认选择的文件名
int result = fileChooser.showSaveDialog(jp); // 显示对话框
if (result == JFileChooser.APPROVE_OPTION) {
File fileToSave = fileChooser.getSelectedFile();
// 构建解密后的文件路径
String decryptedFilePath = fileToSave.getAbsolutePath();
// 获取用户注册时的秘钥
byte[] key = userKey.getBytes();
// 解密文件
try {
xor.decryptFile("data/" + ID + "-Dir/" + selectedFile.getName(), decryptedFilePath, key);
JOptionPane.showMessageDialog(jp, "下载成功!");
} catch (IOException ex) {
JOptionPane.showMessageDialog(jp, "文件解密或写入时发生错误");
ex.printStackTrace();
}
}
}
}
} else {
JOptionPane.showMessageDialog(jp, "密码错误!");
}
});
效果展示
最后,为了使界面的跳转更符合操作,我加上了各种关闭界面和打开界面
public void openLoginUI() {
frame.setVisible(true);
}
public void closeLoginUI() {
frame.setVisible(false);
}
public void openEnrollUI() {
enrollUI();
}
public void closeEnrollUI() {
enrollFrame.setVisible(false);
}
public void closeUserInfoUI() {
userFrame.setVisible(false);
}
public void openUserInfoUI() {
userFrame.setVisible(true);
}