dev文件加解密操作 #1
@@ -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;
|
||||
}
|
||||
|
||||
206
src/test/java/EncryptionAndDecryptionTest.java
Normal file
206
src/test/java/EncryptionAndDecryptionTest.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user