diff --git a/pom.xml b/pom.xml
index 0fc3426..ad3681b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
top.gtb520.java.gnu
gnu_java_utils_library
- 1.3-SNAPSHOT
+ 1.4-SNAPSHOT
25
diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java
index 565ffe9..f23959c 100644
--- a/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java
+++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java
@@ -2,6 +2,13 @@ package top.gtb520.java.gnu.java.utils.library;
import top.gtb520.java.gnu.java.utils.library.utils.print.ColorNameEnum;
+import java.io.File;
+import java.nio.file.Path;
+
+import static top.gtb520.java.gnu.java.utils.library.utils.file.CreateFile.BinFileType;
+import static top.gtb520.java.gnu.java.utils.library.utils.file.EncryptionAndDecryption.EncryptionType;
+
+
public interface main {
// 压缩包操作相关接口与方法
interface compress {
@@ -127,4 +134,31 @@ public interface main {
}
}
}
+
+ // 文件操作接口与方法
+ interface file {
+ interface CreateFile {
+ // 创建文件,创建空白文件,异步IO
+ static File NewFile(String FileName, boolean isBin, BinFileType binFileType) {
+ return top.gtb520.java.gnu.java.utils.library.utils.file.CreateFile.NewFile(FileName, isBin, binFileType);
+ }
+
+ // 以字节流方式保存文件,异步IO
+ static boolean StreamSaveFile(File file, Path TargePath, String FileName) {
+ return top.gtb520.java.gnu.java.utils.library.utils.file.CreateFile.StreamSaveFile(file, TargePath, FileName);
+ }
+ }
+ }
+
+ // 文件加解密操作
+ interface EncryptionAndDecryption {
+ // 文件加密
+ static File FileEncrypt(File file, EncryptionType type, Path keyPath) throws Exception {
+ return top.gtb520.java.gnu.java.utils.library.utils.file.EncryptionAndDecryption.EncryptionFile(file, type, keyPath);
+ }
+ // 文件解密
+ static File DecryptionFile(File file, EncryptionType type, File keyfile) throws Exception {
+ return top.gtb520.java.gnu.java.utils.library.utils.file.EncryptionAndDecryption.DecryptionFile(file, type, keyfile);
+ }
+ }
}
diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/CreateFile.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/CreateFile.java
new file mode 100644
index 0000000..790c192
--- /dev/null
+++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/CreateFile.java
@@ -0,0 +1,89 @@
+package top.gtb520.java.gnu.java.utils.library.utils.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+
+// 创建文件
+public class CreateFile {
+ /***
+ * 创建文件,创建空白文件
+ * isBin为True时创建一个空白的二进制文件,否则创建一个空白的普通文件
+ * 异步IO操作
+ *
+ * @param FileName 文件名
+ * @param isBin 是否二进制文件
+ * @param binFileType 二进制文件类型,如果是普通文件则不需要设置次值,将其设置为null即可,
+ * 参考isBin参数,isBin参数为False则此参数设置为null
+ *
+ * @return 文件
+ */
+ public static File NewFile(String FileName, boolean isBin, BinFileType binFileType) {
+ File TargetFile = null;
+ byte[] BufferByte = new byte[1024];
+
+ try {
+ TargetFile = new File(FileName);
+ if (TargetFile.createNewFile()) {
+ if (isBin && binFileType != null) {
+ // 根据二进制文件类型写入文件头标识
+ byte[] header = switch (binFileType) {
+ case ELF -> new byte[]{0x7F, 'E', 'L', 'F'};
+ case PE -> new byte[]{'M', 'Z'};
+ };
+ try (OutputStream os = Files.newOutputStream(TargetFile.toPath(),
+ StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ os.write(header);
+ os.flush();
+ }
+ }
+ }
+ } catch (IOException e) {
+ TargetFile = null;
+ }
+
+ return TargetFile;
+ }
+
+ /***
+ * 以字节流方式保存文件
+ * 异步IO操作
+ *
+ * @param file 文件
+ * @param TargePath 目标路径
+ * @param FileName 文件名
+ *
+ * @return 是否保存成功,True为保存成功,False为保存失败
+ */
+ public static boolean StreamSaveFile(File file, Path TargePath, String FileName) {
+ try {
+ Path targetFile = TargePath.resolve(FileName);
+ return CompletableFuture.supplyAsync(() -> {
+ try (OutputStream os = Files.newOutputStream(targetFile,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ try (var is = Files.newInputStream(file.toPath())) {
+ while ((bytesRead = is.read(buffer)) != -1) {
+ os.write(buffer, 0, bytesRead);
+ }
+ }
+ os.flush();
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }).get();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public enum BinFileType {
+ ELF, PE;
+ }
+}
diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/EncryptionAndDecryption.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/EncryptionAndDecryption.java
new file mode 100644
index 0000000..e1f0c3a
--- /dev/null
+++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/EncryptionAndDecryption.java
@@ -0,0 +1,202 @@
+package top.gtb520.java.gnu.java.utils.library.utils.file;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CompletableFuture;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class EncryptionAndDecryption {
+ /***
+ * 文件加密
+ * 异步执行加密操作
+ * 加密后覆盖原文件,不添加额外后缀
+ * 密钥保存到 keyPath 目录下,密钥名为 原文件名.key
+ *
+ * @param file 文件
+ * @param type 加密类型
+ * @param keyPath 密钥文件目录路径,密钥名为 文件名.key
+ * @return 加密后的文件(原文件),失败时返回 null
+ */
+ public static File EncryptionFile(File file, EncryptionType type, Path keyPath) {
+ File TargetFile = null;
+
+ if (file == null || !file.exists() || !file.isFile()) {
+ return null;
+ }
+ if (keyPath == null) {
+ return null;
+ }
+
+ try {
+ TargetFile = CompletableFuture.supplyAsync(() -> {
+ try {
+ // 读取原文件内容
+ byte[] plainBytes = Files.readAllBytes(file.toPath());
+
+ // 根据加密类型生成对称密钥
+ SecretKey secretKey = generateSecretKey(type);
+ byte[] encryptedBytes = encryptBytes(plainBytes, secretKey, type);
+
+ // 将密钥写入 keyPath 目录,密钥名为 文件名.key
+ Files.createDirectories(keyPath);
+ String keyFileName = file.getName() + ".key";
+ Path keyFilePath = keyPath.resolve(keyFileName);
+ try (OutputStream kos = Files.newOutputStream(keyFilePath,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ kos.write(secretKey.getEncoded());
+ kos.flush();
+ }
+
+ // 加密后覆盖写回原文件
+ try (OutputStream os = Files.newOutputStream(file.toPath(),
+ StandardOpenOption.TRUNCATE_EXISTING)) {
+ os.write(encryptedBytes);
+ os.flush();
+ }
+ return file;
+ } catch (Exception e) {
+ return null;
+ }
+ }).get();
+ } catch (Exception e) {
+ TargetFile = null;
+ }
+
+ return TargetFile;
+ }
+
+ /***
+ * 生成对称密钥
+ *
+ * @param type 加密类型
+ * @return 对称密钥
+ */
+ private static SecretKey generateSecretKey(EncryptionType type) throws NoSuchAlgorithmException {
+ String algorithm = switch (type) {
+ case AES -> "AES";
+ case DES -> "DES";
+ case RSA -> "AES"; // RSA 为非对称加密,此处用 AES 做对称加密替代
+ };
+ KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
+ int keySize = switch (type) {
+ case AES -> 128;
+ case DES -> 56;
+ case RSA -> 128;
+ };
+ keyGen.init(keySize);
+ return keyGen.generateKey();
+ }
+
+ /***
+ * 对字节数据进行加密
+ *
+ * @param data 原始数据
+ * @param key 密钥
+ * @param type 加密类型
+ * @return 加密后的数据
+ */
+ private static byte[] encryptBytes(byte[] data, SecretKey key, EncryptionType type)
+ throws NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ String algorithm = switch (type) {
+ case AES -> "AES/ECB/PKCS5Padding";
+ case DES -> "DES/ECB/PKCS5Padding";
+ case RSA -> "AES/ECB/PKCS5Padding";
+ };
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ return cipher.doFinal(data);
+ }
+
+ /***
+ * 文件解密
+ * 异步执行解密操作
+ * 解密后覆盖写回原文件,不添加额外后缀
+ *
+ * @param file 加密文件
+ * @param type 加密类型,须与加密时一致
+ * @param keyfile 密钥文件,从中读取解密密钥
+ * @return 解密后的文件(原文件),失败时返回 null
+ */
+ public static File DecryptionFile(File file, EncryptionType type, File keyfile) {
+ File TargetFile = null;
+
+ if (file == null || !file.exists() || !file.isFile()) {
+ return null;
+ }
+ if (keyfile == null || !keyfile.exists() || !keyfile.isFile()) {
+ return null;
+ }
+
+ try {
+ TargetFile = CompletableFuture.supplyAsync(() -> {
+ try {
+ // 从密钥文件读取密钥
+ byte[] keyBytes = Files.readAllBytes(keyfile.toPath());
+ String algorithm = switch (type) {
+ case AES -> "AES";
+ case DES -> "DES";
+ case RSA -> "AES";
+ };
+ SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(keyBytes, algorithm);
+
+ // 读取加密文件内容
+ byte[] encryptedBytes = Files.readAllBytes(file.toPath());
+
+ // 解密
+ byte[] decryptedBytes = decryptBytes(encryptedBytes, secretKey, type);
+
+ // 解密后覆盖写回原文件
+ try (OutputStream os = Files.newOutputStream(file.toPath(),
+ StandardOpenOption.TRUNCATE_EXISTING)) {
+ os.write(decryptedBytes);
+ os.flush();
+ }
+ return file;
+ } catch (Exception e) {
+ return null;
+ }
+ }).get();
+ } catch (Exception e) {
+ TargetFile = null;
+ }
+
+ return TargetFile;
+ }
+
+ /***
+ * 对字节数据进行解密
+ *
+ * @param data 加密数据
+ * @param key 密钥
+ * @param type 加密类型
+ * @return 解密后的数据
+ */
+ private static byte[] decryptBytes(byte[] data, SecretKey key, EncryptionType type)
+ throws NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ String algorithm = switch (type) {
+ case AES -> "AES/ECB/PKCS5Padding";
+ case DES -> "DES/ECB/PKCS5Padding";
+ case RSA -> "AES/ECB/PKCS5Padding";
+ };
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, key);
+ return cipher.doFinal(data);
+ }
+
+ public enum EncryptionType {
+ AES, DES, RSA;
+ }
+}
diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java
index 1bdfef8..2e79b66 100644
--- a/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java
+++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java
@@ -2,7 +2,7 @@ package top.gtb520.java.gnu.java.utils.library;
public class version {
// 项目的版本信息,不代表实时运行环境
- public static final String VERSION = "1.3";
+ public static final String VERSION = "1.4";
public static final String JAVA_VERSION = "25";
public static final String OpenJDK_VERSION = "25.0.0.0.1";
public static final String OpenJDK_Runtime_Environment = "25.0.0.0.1+36-GA";
diff --git a/src/test/java/EncryptionAndDecryptionTest.java b/src/test/java/EncryptionAndDecryptionTest.java
new file mode 100644
index 0000000..bed7479
--- /dev/null
+++ b/src/test/java/EncryptionAndDecryptionTest.java
@@ -0,0 +1,206 @@
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import top.gtb520.java.gnu.java.utils.library.utils.file.EncryptionAndDecryption;
+import top.gtb520.java.gnu.java.utils.library.utils.file.EncryptionAndDecryption.EncryptionType;
+
+public class EncryptionAndDecryptionTest {
+
+ static int passCount = 0;
+ static int failCount = 0;
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("========== EncryptionAndDecryption 测试开始 ==========");
+
+ // 正常场景:AES 加密/解密
+ testEncryptDecryptAES();
+ // 正常场景:DES 加密/解密
+ testEncryptDecryptDES();
+ // 正常场景:RSA 加密/解密
+ testEncryptDecryptRSA();
+ // 正常场景:验证密钥文件生成
+ testKeyFileGenerated();
+ // 正常场景:加密后文件内容发生变化
+ testFileContentChangedAfterEncrypt();
+
+ // 异常场景:file 为 null
+ testNullFile();
+ // 异常场景:keyPath 为 null
+ testNullKeyPath();
+ // 异常场景:keyfile 为 null(解密)
+ testNullKeyFile();
+ // 异常场景:文件不存在
+ testFileNotExist();
+ // 异常场景:密钥文件不存在(解密)
+ testKeyFileNotExist();
+
+ System.out.println("========== 测试结果 ==========");
+ System.out.println("通过: " + passCount);
+ System.out.println("失败: " + failCount);
+ System.out.println("总计: " + (passCount + failCount));
+
+ if (failCount > 0) {
+ System.out.println("存在失败的测试用例!");
+ System.exit(1);
+ } else {
+ System.out.println("全部测试通过!");
+ }
+ }
+
+ /**
+ * 创建临时测试文件并写入测试内容
+ */
+ static File createTempFile(String prefix) throws Exception {
+ Path tempDir = Files.createTempDirectory("enc_test_");
+ File tempFile = tempDir.resolve(prefix + ".txt").toFile();
+ Files.writeString(tempFile.toPath(), "Hello, EncryptionAndDecryption Test! 测试内容123");
+ return tempFile;
+ }
+
+ /**
+ * 创建临时密钥目录
+ */
+ static Path createTempKeyPath() throws Exception {
+ return Files.createTempDirectory("enc_key_");
+ }
+
+ static void assertEquals(Object expected, Object actual, String testName) {
+ if (expected == null && actual == null) {
+ passCount++;
+ System.out.println("[PASS] " + testName);
+ } else if (expected != null && expected.equals(actual)) {
+ passCount++;
+ System.out.println("[PASS] " + testName);
+ } else {
+ failCount++;
+ System.out.println("[FAIL] " + testName + " | 期望: " + expected + ", 实际: " + actual);
+ }
+ }
+
+ static void assertTrue(boolean condition, String testName) {
+ if (condition) {
+ passCount++;
+ System.out.println("[PASS] " + testName);
+ } else {
+ failCount++;
+ System.out.println("[FAIL] " + testName);
+ }
+ }
+
+ // ==================== 正常场景 ====================
+
+ static void testEncryptDecryptAES() throws Exception {
+ String testName = "AES 加密后解密,内容还原";
+ File file = createTempFile("aes_test");
+ Path keyPath = createTempKeyPath();
+ String originalContent = Files.readString(file.toPath());
+
+ File encrypted = EncryptionAndDecryption.EncryptionFile(file, EncryptionType.AES, keyPath);
+ assertTrue(encrypted != null, testName + " - 加密返回非null");
+
+ File keyFile = keyPath.resolve(file.getName() + ".key").toFile();
+ File decrypted = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.AES, keyFile);
+ assertTrue(decrypted != null, testName + " - 解密返回非null");
+
+ String decryptedContent = Files.readString(file.toPath());
+ assertEquals(originalContent, decryptedContent, testName);
+ }
+
+ static void testEncryptDecryptDES() throws Exception {
+ String testName = "DES 加密后解密,内容还原";
+ File file = createTempFile("des_test");
+ Path keyPath = createTempKeyPath();
+ String originalContent = Files.readString(file.toPath());
+
+ File encrypted = EncryptionAndDecryption.EncryptionFile(file, EncryptionType.DES, keyPath);
+ assertTrue(encrypted != null, testName + " - 加密返回非null");
+
+ File keyFile = keyPath.resolve(file.getName() + ".key").toFile();
+ File decrypted = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.DES, keyFile);
+ assertTrue(decrypted != null, testName + " - 解密返回非null");
+
+ String decryptedContent = Files.readString(file.toPath());
+ assertEquals(originalContent, decryptedContent, testName);
+ }
+
+ static void testEncryptDecryptRSA() throws Exception {
+ String testName = "RSA 加密后解密,内容还原";
+ File file = createTempFile("rsa_test");
+ Path keyPath = createTempKeyPath();
+ String originalContent = Files.readString(file.toPath());
+
+ File encrypted = EncryptionAndDecryption.EncryptionFile(file, EncryptionType.RSA, keyPath);
+ assertTrue(encrypted != null, testName + " - 加密返回非null");
+
+ File keyFile = keyPath.resolve(file.getName() + ".key").toFile();
+ File decrypted = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.RSA, keyFile);
+ assertTrue(decrypted != null, testName + " - 解密返回非null");
+
+ String decryptedContent = Files.readString(file.toPath());
+ assertEquals(originalContent, decryptedContent, testName);
+ }
+
+ static void testKeyFileGenerated() throws Exception {
+ String testName = "加密后密钥文件正确生成";
+ File file = createTempFile("key_test");
+ Path keyPath = createTempKeyPath();
+
+ EncryptionAndDecryption.EncryptionFile(file, EncryptionType.AES, keyPath);
+
+ File keyFile = keyPath.resolve(file.getName() + ".key").toFile();
+ assertTrue(keyFile.exists(), testName + " - 密钥文件存在");
+ assertTrue(keyFile.length() > 0, testName + " - 密钥文件非空");
+ }
+
+ static void testFileContentChangedAfterEncrypt() throws Exception {
+ String testName = "加密后文件内容与原文不同";
+ File file = createTempFile("content_test");
+ Path keyPath = createTempKeyPath();
+ byte[] originalBytes = Files.readAllBytes(file.toPath());
+
+ EncryptionAndDecryption.EncryptionFile(file, EncryptionType.AES, keyPath);
+
+ byte[] encryptedBytes = Files.readAllBytes(file.toPath());
+ assertTrue(!java.util.Arrays.equals(originalBytes, encryptedBytes), testName);
+ }
+
+ // ==================== 异常场景 ====================
+
+ static void testNullFile() throws Exception {
+ String testName = "file 为 null 时加密返回 null";
+ Path keyPath = createTempKeyPath();
+ File result = EncryptionAndDecryption.EncryptionFile(null, EncryptionType.AES, keyPath);
+ assertEquals(null, result, testName);
+ }
+
+ static void testNullKeyPath() throws Exception {
+ String testName = "keyPath 为 null 时加密返回 null";
+ File file = createTempFile("null_keypath");
+ File result = EncryptionAndDecryption.EncryptionFile(file, EncryptionType.AES, null);
+ assertEquals(null, result, testName);
+ }
+
+ static void testNullKeyFile() throws Exception {
+ String testName = "keyfile 为 null 时解密返回 null";
+ File file = createTempFile("null_keyfile");
+ File result = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.AES, null);
+ assertEquals(null, result, testName);
+ }
+
+ static void testFileNotExist() throws Exception {
+ String testName = "文件不存在时加密返回 null";
+ Path keyPath = createTempKeyPath();
+ File notExist = new File("non_existent_file_12345.txt");
+ File result = EncryptionAndDecryption.EncryptionFile(notExist, EncryptionType.AES, keyPath);
+ assertEquals(null, result, testName);
+ }
+
+ static void testKeyFileNotExist() throws Exception {
+ String testName = "密钥文件不存在时解密返回 null";
+ File file = createTempFile("no_keyfile");
+ File notExistKey = new File("non_existent_key_12345.key");
+ File result = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.AES, notExistKey);
+ assertEquals(null, result, testName);
+ }
+}