java使用AES-256-CBC算法对文件或字符串进行加密解密

之前整理过一篇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>
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容