之前整理过一篇java实现AES对称加密解密
这个是AES-128算法,近期有使用AES-256-CBC算法加解密的需求,再整理记录下这个。
实现代码如下,里面main方法有对文件加解密和对字符串加解密的调用方法,注意,AES-256对jdk版本有要求,需要满足JDK 8u161 或更高版本,我本地使用的jdk版本是jdk1.8.0_202
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AesUtil {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256";
// 密钥派生参数
private static final int ITERATION_COUNT = 100000; // 迭代次数
private static final int KEY_LENGTH = 256; // AES-256
private static final int SALT_LENGTH = 16; // 16字节盐
private static final int IV_LENGTH = 16; // 16字节IV
private static final int BUFFER_SIZE = 64 * 1024; // 64KB缓冲区
/**
* 获取支持 AES-256 的 Cipher 实例
*/
public static Cipher getAESCipher() throws Exception {
return Cipher.getInstance(TRANSFORMATION);
}
/**
* 加密文件
*
* @param inputFile 原始文件路径
* @param outputFile 加密后的文件路径
* @param password 加密密码
* @throws Exception 加密异常
*/
public static void encryptFile(Path inputFile, Path outputFile, String password) throws Exception {
// 1. 生成随机盐
byte[] salt = generateRandomBytes(SALT_LENGTH);
// 2. 生成随机IV
byte[] iv = generateRandomBytes(IV_LENGTH);
// 3. 从密码和盐派生密钥
SecretKey secretKey = deriveKey(password, salt);
// 4. 初始化加密器
Cipher cipher = getAESCipher();
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 5. 加密文件(先写入盐和IV)
try (FileOutputStream fos = new FileOutputStream(outputFile.toFile());
BufferedOutputStream bos = new BufferedOutputStream(fos);
FileInputStream fis = new FileInputStream(inputFile.toFile());
BufferedInputStream bis = new BufferedInputStream(fis);
CipherOutputStream cos = new CipherOutputStream(bos, cipher)) {
// 写入盐(16字节)
bos.write(salt);
// 写入IV(16字节)
bos.write(iv);
// 加密文件内容
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
cos.flush();
}
System.out.println("加密完成: " + outputFile);
System.out.println("盐 (Base64): " + Base64.getEncoder().encodeToString(salt));
System.out.println("IV (Base64): " + Base64.getEncoder().encodeToString(iv));
}
/**
* 解密文件
*
* @param inputFile 加密的文件路径
* @param outputFile 解密后的文件路径
* @param password 解密密码
* @throws Exception 解密异常
*/
public static void decryptFile(Path inputFile, Path outputFile, String password) throws Exception {
// 1. 读取盐和IV
try (FileInputStream fis = new FileInputStream(inputFile.toFile());
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 读取盐(前16字节)
byte[] salt = new byte[SALT_LENGTH];
int saltRead = bis.read(salt);
if (saltRead != SALT_LENGTH) {
throw new IOException("无效的文件格式: 盐读取失败");
}
// 读取IV(接下来的16字节)
byte[] iv = new byte[IV_LENGTH];
int ivRead = bis.read(iv);
if (ivRead != IV_LENGTH) {
throw new IOException("无效的文件格式: IV读取失败");
}
// 2. 从密码和盐派生密钥
SecretKey secretKey = deriveKey(password, salt);
// 3. 初始化解密器
Cipher cipher = getAESCipher();
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 4. 解密文件
try (FileOutputStream fos = new FileOutputStream(outputFile.toFile());
BufferedOutputStream bos = new BufferedOutputStream(fos);
CipherInputStream cis = new CipherInputStream(bis, cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = cis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
}
System.out.println("解密完成: " + outputFile);
}
}
/**
* 从密码派生AES密钥(使用PBKDF2)
*/
private static SecretKey deriveKey(String password, byte[] salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), ALGORITHM);
}
/**
* 生成随机字节数组
*/
private static byte[] generateRandomBytes(int length) {
byte[] bytes = new byte[length];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(bytes);
return bytes;
}
/**
* 加密文件(带进度回调)
*/
public static void encryptFileWithProgress(
Path inputFile,
Path outputFile,
String password,
ProgressCallback progressCallback) throws Exception {
long fileSize = Files.size(inputFile);
long processedSize = 0;
byte[] salt = generateRandomBytes(SALT_LENGTH);
byte[] iv = generateRandomBytes(IV_LENGTH);
SecretKey secretKey = deriveKey(password, salt);
Cipher cipher = getAESCipher();
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
try (FileOutputStream fos = new FileOutputStream(outputFile.toFile());
BufferedOutputStream bos = new BufferedOutputStream(fos);
FileInputStream fis = new FileInputStream(inputFile.toFile());
BufferedInputStream bis = new BufferedInputStream(fis);
CipherOutputStream cos = new CipherOutputStream(bos, cipher)) {
bos.write(salt);
bos.write(iv);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
processedSize += bytesRead;
if (progressCallback != null) {
progressCallback.onProgress(processedSize, fileSize);
}
}
cos.flush();
}
}
/**
* 进度回调接口
*/
public interface ProgressCallback {
void onProgress(long processed, long total);
}
/**
* 加密字符串
*
* @param plainText 原始字符串
* @param password 加密密码
* @return Base64 编码的加密字符串(包含盐和IV)
* @throws Exception 加密异常
*/
public static String encrypt(String plainText, String password) throws Exception {
// 1. 生成随机盐
byte[] salt = generateRandomBytes(SALT_LENGTH);
// 2. 生成随机IV
byte[] iv = generateRandomBytes(IV_LENGTH);
// 3. 从密码和盐派生密钥
SecretKey secretKey = deriveKey(password, salt);
// 4. 初始化加密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 5. 加密数据
byte[] encryptedData = cipher.doFinal(plainText.getBytes("UTF-8"));
// 6. 组合:盐 + IV + 密文
byte[] combined = new byte[salt.length + iv.length + encryptedData.length];
System.arraycopy(salt, 0, combined, 0, salt.length);
System.arraycopy(iv, 0, combined, salt.length, iv.length);
System.arraycopy(encryptedData, 0, combined, salt.length + iv.length, encryptedData.length);
// 7. Base64 编码
return Base64.getEncoder().encodeToString(combined);
}
/**
* 解密字符串
*
* @param encryptedBase64 Base64 编码的加密字符串
* @param password 解密密码
* @return 解密后的原始字符串
* @throws Exception 解密异常
*/
public static String decrypt(String encryptedBase64, String password) throws Exception {
// 1. Base64 解码
byte[] combined = Base64.getDecoder().decode(encryptedBase64);
// 2. 提取盐、IV和密文
if (combined.length < SALT_LENGTH + IV_LENGTH) {
throw new IllegalArgumentException("无效的加密数据格式");
}
byte[] salt = new byte[SALT_LENGTH];
byte[] iv = new byte[IV_LENGTH];
byte[] encryptedData = new byte[combined.length - SALT_LENGTH - IV_LENGTH];
System.arraycopy(combined, 0, salt, 0, SALT_LENGTH);
System.arraycopy(combined, SALT_LENGTH, iv, 0, IV_LENGTH);
System.arraycopy(combined, SALT_LENGTH + IV_LENGTH, encryptedData, 0, encryptedData.length);
// 3. 从密码和盐派生密钥
SecretKey secretKey = deriveKey(password, salt);
// 4. 初始化解密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 5. 解密数据
byte[] decryptedData = cipher.doFinal(encryptedData);
// 6. 返回原始字符串
return new String(decryptedData, "UTF-8");
}
/**
* 使用示例
*/
public static void main1(String[] args) {
try {
String originalText = "Hello, 这是一个测试字符串!AES-256-CBC 加密测试。";
String password = "MyStrongPassword123!@#";
System.out.println("=== AES-256-CBC 字符串加密/解密示例 ===");
System.out.println("原始文本: " + originalText);
System.out.println("密码: " + password);
System.out.println();
// 1. 加密
long startTime = System.currentTimeMillis();
String encryptedBase64 = encrypt(originalText, password);
long endTime = System.currentTimeMillis();
System.out.println("加密后 (Base64): " + encryptedBase64);
System.out.println("加密长度: " + encryptedBase64.length() + " 字符");
System.out.printf("加密耗时: %d ms%n%n", (endTime - startTime));
// 2. 解密
startTime = System.currentTimeMillis();
String decryptedText = decrypt(encryptedBase64, password);
endTime = System.currentTimeMillis();
System.out.println("解密后: " + decryptedText);
System.out.printf("解密耗时: %d ms%n", (endTime - startTime));
// 3. 验证
if (originalText.equals(decryptedText)) {
System.out.println("\n✓ 加密解密验证成功!");
} else {
System.err.println("\n✗ 加密解密验证失败!");
}
// 4. 演示:相同明文不同密码结果不同
System.out.println("\n=== 相同明文不同密码 ===");
String anotherPassword = "AnotherPassword456!@#";
String encryptedWithAnother = encrypt(originalText, anotherPassword);
System.out.println("使用密码1加密: " + encryptedBase64.substring(0, 50) + "...");
System.out.println("使用密码2加密: " + encryptedWithAnother.substring(0, 50) + "...");
System.out.println("两次加密结果不同(盐和IV随机生成)");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用示例
*/
public static void main2(String[] args) {
try {
Path inputFile = Paths.get("D:\\tmp\\zipTest\\source\\2024test.zip");
Path encryptedFile = Paths.get("D:\\tmp\\zipTest\\targert\\2024test.zip.enc");
Path decryptedFile = Paths.get("D:\\tmp\\zipTest\\targert\\2024test.zip");
String password = "1234567890";
// 1. 加密文件
System.out.println("=== 开始加密 ===");
long startTime = System.currentTimeMillis();
encryptFile(inputFile, encryptedFile, password);
// encryptFileWithProgress(inputFile, encryptedFile, password,
// (processed, total) -> {
// double progress = (double) processed / total * 100;
// System.out.printf("加密进度: %.2f%% (%d/%d 字节)%n",
// progress, processed, total);
// }
// );
long endTime = System.currentTimeMillis();
System.out.printf("加密耗时: %.2f秒%n%n", (endTime - startTime) / 1000.0);
// 2. 解密文件(验证)
System.out.println("=== 开始解密 ===");
startTime = System.currentTimeMillis();
decryptFile(encryptedFile, decryptedFile, password);
endTime = System.currentTimeMillis();
System.out.printf("解密耗时: %.2f秒%n", (endTime - startTime) / 1000.0);
// 3. 验证文件是否一致
if (Files.exists(decryptedFile)) {
System.out.println("\n✓ 解密成功!文件已恢复");
System.out.println("原始文件: " + inputFile);
System.out.println("解密文件: " + decryptedFile);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
System.out.println("最大允许的 AES 密钥长度(位): " + maxKeyLen);
if (maxKeyLen < 256) {
System.out.println("⚠️ 当前 JDK 不支持 AES-256,需要安装 JCE 无限制策略");
} else {
System.out.println("✅ 当前 JDK 支持 AES-256");
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
补充 maven 依赖包引入:
<!-- 引入zstd和lz4压缩算法 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.28.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/com.github.luben/zstd-jni -->
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.7-13</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.lz4/lz4-java -->
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>1.8.1</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.22.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>