From 9d452ded627eef8227a14ed1cbc87c6fbee14b17 Mon Sep 17 00:00:00 2001 From: database-mysql Date: Wed, 12 Aug 2026 03:43:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8A=A0=E8=A7=A3=E5=AF=86?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/file/EncryptionAndDecryption.java | 74 ++++--- .../java/EncryptionAndDecryptionTest.java | 206 ++++++++++++++++++ 2 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 src/test/java/EncryptionAndDecryptionTest.java 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 index 9073c80..e1f0c3a 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -19,18 +20,23 @@ public class EncryptionAndDecryption { /*** * 文件加密 * 异步执行加密操作 - * 加密后的文件保存为原文件名加 .enc 后缀,位于同目录下 + * 加密后覆盖原文件,不添加额外后缀 + * 密钥保存到 keyPath 目录下,密钥名为 原文件名.key * * @param file 文件 * @param type 加密类型 - * @return 加密后的文件,失败时返回 null + * @param keyPath 密钥文件目录路径,密钥名为 文件名.key + * @return 加密后的文件(原文件),失败时返回 null */ - public static File EncryptionFile(File file, EncryptionType type) { + 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(() -> { @@ -42,18 +48,23 @@ public class EncryptionAndDecryption { SecretKey secretKey = generateSecretKey(type); byte[] encryptedBytes = encryptBytes(plainBytes, secretKey, type); - // 写入加密后的文件,文件格式:[1字节密钥长度][密钥字节][加密数据] - byte[] keyBytes = secretKey.getEncoded(); - String encryptedFileName = file.getName() + ".enc"; - File encryptedFile = new File(file.getParentFile(), encryptedFileName); - try (OutputStream os = Files.newOutputStream(encryptedFile.toPath(), + // 将密钥写入 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)) { - os.write(keyBytes.length); - os.write(keyBytes); + kos.write(secretKey.getEncoded()); + kos.flush(); + } + + // 加密后覆盖写回原文件 + try (OutputStream os = Files.newOutputStream(file.toPath(), + StandardOpenOption.TRUNCATE_EXISTING)) { os.write(encryptedBytes); os.flush(); } - return encryptedFile; + return file; } catch (Exception e) { return null; } @@ -111,30 +122,28 @@ public class EncryptionAndDecryption { /*** * 文件解密 * 异步执行解密操作 - * 解密后的文件去除 .enc 后缀,保存在同目录下 + * 解密后覆盖写回原文件,不添加额外后缀 * - * @param file 加密文件(.enc) + * @param file 加密文件 * @param type 加密类型,须与加密时一致 - * @return 解密后的文件,失败时返回 null + * @param keyfile 密钥文件,从中读取解密密钥 + * @return 解密后的文件(原文件),失败时返回 null */ - public static File DecryptionFile(File file, EncryptionType type) { + 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[] fileBytes = Files.readAllBytes(file.toPath()); - - // 读取文件头中的密钥长度和密钥 - int keyLength = fileBytes[0] & 0xFF; - byte[] keyBytes = new byte[keyLength]; - System.arraycopy(fileBytes, 1, keyBytes, 0, keyLength); - - // 还原密钥 + // 从密钥文件读取密钥 + byte[] keyBytes = Files.readAllBytes(keyfile.toPath()); String algorithm = switch (type) { case AES -> "AES"; case DES -> "DES"; @@ -142,24 +151,19 @@ public class EncryptionAndDecryption { }; SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(keyBytes, algorithm); - // 提取加密数据部分 - byte[] encryptedBytes = new byte[fileBytes.length - 1 - keyLength]; - System.arraycopy(fileBytes, 1 + keyLength, encryptedBytes, 0, encryptedBytes.length); + // 读取加密文件内容 + byte[] encryptedBytes = Files.readAllBytes(file.toPath()); + // 解密 byte[] decryptedBytes = decryptBytes(encryptedBytes, secretKey, type); - // 生成解密后的文件名,去除 .enc 后缀 - String fileName = file.getName(); - String decryptedFileName = fileName.endsWith(".enc") - ? fileName.substring(0, fileName.length() - 4) - : fileName + ".dec"; - File decryptedFile = new File(file.getParentFile(), decryptedFileName); - try (OutputStream os = Files.newOutputStream(decryptedFile.toPath(), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + // 解密后覆盖写回原文件 + try (OutputStream os = Files.newOutputStream(file.toPath(), + StandardOpenOption.TRUNCATE_EXISTING)) { os.write(decryptedBytes); os.flush(); } - return decryptedFile; + return file; } catch (Exception e) { return null; } 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); + } +}