# java 压缩与解压文件 - 加密与解密文件

# Java KeyGenerator 加密的概述:

AES/CBC/PKCS5Padding 是一种常见的加密配置组合,用于加密和解密数据。以下是每个部分的详细解释

# AES(Advanced Encryption Standard)

AES (高级加密标准) 是一种对称加密算法,用于加密和解密数据。它支持三种不同的密钥长度:

128 位,192 位和 256 位。AES 是一种分组密码,每次处理固定长度的数据块 (128 位,即 16 字节)

# CBC(Cipher Block Chaining)

CBC (密码分组链接) 是一种分组密码的工作模式。在 CBC 模式中,每个明文块在加密之前会与前一个密文块进行异或 (XOR) 操作。这使得相同的明文块在加密后会生成不同的密文块,从而增强了安全性。

  • 初始向量 (IV):CBC 模式需要一个初始向量 (IV) 来开始第一个明文块的加密过程。IV 必须是唯一且不可预测的,但不需要保密。IV 的长度与分组大小相同 (对于 AES 是 16 字节)
  • 加密过程:第一个明文块与 IV 进行异或后加密,生成第一个密文块。第二个明文块与第一个密文块异或后加密,生成第二个密文块,以此类推

# PKCS5Padding

PKCS5Padding 是一种填充 (Padding) 方案,用于确保待加密数据的长度是分组大小的整数倍。

AES 是一种分组密码,需要数据块的长度是分组大小的整数倍 (16 字节)。如果待加密数据的长度不是 16 字节的整数倍,就需要填充。

  • 填充规则:PKCS5Padding 规定每个填充字节的值等于填充的字节数。例如,如果需要填充 4 个字节,则每个填充字节的值等于填充的字节数。例如,如果需要填充 4 个字节,则每个填充字节的值为 0x04 。如果需要填充 1 个字节,则填充值为 0x01 。如果数据长度正好是分组大小的整数倍,则添加一个完整的填充块 (16 字节,每个字节值为 0x10 )

# 结合在一起

AES/CBC/PKCS5Padding 结合使用时,数据先经过填充 (如果需要),然后分块。每个数据块在加密前会与前一个密文块异或,增强了安全性

明文 -> 填充 -> 分块 -> AES加密 (使用CBC模式和IV)

# 总结:

AES/CBC/PKCS5Padding 是一种安全的加密方案,适用于许多应用场景。AES 提供强大的加密能力,CBC 模式通过链式加密提高安全性,PKCS5Padding 确保数据块的长度适合加密处理。

# 代码如下:

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
 * zipIO 学习
 *
 * @author: 窦凯欣
 * @date: 2024-07-30 11:20
 **/
@SuppressWarnings("all")
public class Demo {
    // 加密算法
    private static final String ALGORITHM = "AES";
    // 转型
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    // 加密秘钥大小
    private static final int KEY_SIZE = 128;
    private static final int IV_SIZE = 16;
    public static void main(String[] args) {
        // 压缩文件
        String fileName = "D:\\决策支持平台\\五年规划报告模板";
        String descName = "D:\\解压缩目录\\刚压缩.zip";
        new Demo().zipFile(fileName, descName);
        System.out.println("压缩文件完毕!");
        // 解压缩文件
        String zipFileName = "D:\\解压缩目录\\刚压缩.zip";
        String descFileName = "D:\\解压缩目录\\";
         new Demo().unzipFile(zipFileName, descFileName);
         System.out.println("文件解压缩完毕!");
        // 生成密钥
        SecretKey secretKey = generateKey();
        // 将密钥保存到磁盘
        saveKey(secretKey, "D:\\secret.txt");
        // 生成 IV
        IvParameterSpec iv = generateIv();
        // IV 保存到磁盘
        saveIv(iv, "D:\\iv.txt");
        System.out.println("密钥与IV生成并保存磁盘完毕,请做检查。");
        // 加密文件
        String fileNamePwd = "D:\\解压缩目录\\五年规划报告模板\\0-天然气管网业务“XX五”发展规划-发展基础(1-43).docx";
        String descFileNamePwd = "D:\\解压缩目录\\加密文件.dat";
        new BugFixes().encryptFile(secretKey, iv, fileNamePwd, descFileNamePwd);
        System.out.println("文件加密完毕!");
        // 从文件加载密钥和 IV
        SecretKey secretKeyHF = loadKey("D:\\secret.txt");
        IvParameterSpec ivHF = loadIv("D:\\iv.txt");
        System.out.println("secretKeyHF = " + secretKeyHF);
        // 解密文件
        String fileNameHF = "D:\\解压缩目录\\加密文件.dat";
        String descFileNameHF = "D:\\解压缩目录\\解密文件.docx";
        new BugFixes().decryptFile(secretKeyHF, ivHF, fileNameHF, descFileNameHF);
        System.out.println("文件解密完毕!");
    }
    // 压缩文件
    public void zipFile(String srcDir, String zipFileName) {
        try (FileOutputStream fos = new FileOutputStream(zipFileName);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            File srcFile = new File(srcDir);
            compressDirectoryToZipFile(srcFile.getParent(), srcFile.getName(), zos);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // 递归目录中的文件进行处理
    private void compressDirectoryToZipFile(String rootDir, String srcDir, ZipOutputStream zos) throws IOException {
        File srcFile = new File(rootDir, srcDir);
        if (srcFile.isDirectory()) {
            File[] files = srcFile.listFiles();
            if(files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        compressDirectoryToZipFile(rootDir, srcDir + File.separator + file.getName(), zos);
                    } else {
                        compressFileToZipFile(rootDir, srcDir + File.separator + file.getName(), zos);
                    }
                }
            }
        } else {
            compressFileToZipFile(rootDir, srcDir, zos);
        }
    }
    // 将传入的文件进行压缩
    private void compressFileToZipFile(String rootDir, String srcFile, ZipOutputStream zos) throws IOException {
        File file = new File(rootDir, srcFile);
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            ZipEntry zipEntry = new ZipEntry(srcFile);
            zos.putNextEntry(zipEntry);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                zos.write(buffer, 0, len);
            }
            zos.closeEntry();
        }
    }
    // 解压缩文件
    private void unzipFile(String zipFileName, String descFileName) {
        String descFileNames = descFileName;
        if (!descFileNames.endsWith(File.separator)) {
            descFileNames = descFileNames + File.separator;
        }
        try (
                InputStream fileStream = Files.newInputStream(Paths.get(zipFileName));
                ZipInputStream zipInputStream = new ZipInputStream(fileStream, Charset.forName("GBK"))
        ) {
            // 根据 ZIP 文件创建 ZipInputStream 对象
            ZipEntry entry;
            while ((entry = zipInputStream.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    // 如果是文件夹,创建目录
                    boolean mkdir = new File(descFileNames + entry.getName()).mkdirs();
                    System.out.println("创建目录" + (mkdir ? "成功" : "失败") + "了");
                    continue;
                }
                // 创建输出文件
                File outFile = new File(descFileNames + entry.getName());
                // 创建父目录
                boolean mkdir = new File(outFile.getParent()).mkdirs();
                System.out.println("创建父目录" + (mkdir ? "成功" : "失败") + "了");
                try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(Files.newOutputStream(outFile.toPath()))) {
                    byte[] buf = new byte[1024];
                    int readByte;
                    while ((readByte = zipInputStream.read(buf)) != -1) {
                        bufferedOutputStream.write(buf, 0, readByte);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // 生成密钥
    public static SecretKey generateKey() {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
            keyGen.init(KEY_SIZE, new SecureRandom());
            return keyGen.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
    // 保存密钥到磁盘
    public static void saveKey(SecretKey key, String filePath) {
        byte[] keyBytes = key.getEncoded();
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(keyBytes);
            System.out.println("keyBytes = " + keyBytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // 生成 IV
    public static IvParameterSpec generateIv() {
        byte[] iv = new byte[IV_SIZE];
        new SecureRandom().nextBytes(iv);
        return new IvParameterSpec(iv);
    }
    // 将 IV 保存到磁盘
    public static void saveIv(IvParameterSpec iv, String filePath) {
        try {
            Files.write(new File(filePath).toPath(), iv.getIV());
            System.out.println("iv.getIV() = " + iv.getIV());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 加密文件
    public void encryptFile(SecretKey secretKey, IvParameterSpec iv, String srcFile, String destFile) {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            try (FileInputStream fis = new FileInputStream(srcFile);
                 FileOutputStream fos = new FileOutputStream(destFile);
                 CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
                byte[] buffer = new byte[1024];
                int read;
                while ((read = fis.read(buffer)) != -1) {
                    cos.write(buffer, 0, read);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // 从文件加载密钥
    public static SecretKey loadKey(String filePath) {
        try {
            byte[] keyBytes = Files.readAllBytes(new File(filePath).toPath());
            return new SecretKeySpec(keyBytes, ALGORITHM);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 从文件加载 IV
    public static IvParameterSpec loadIv(String filePath) {
        try {
            byte[] ivBytes = Files.readAllBytes(new File(filePath).toPath());
            return new IvParameterSpec(ivBytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 解密文件
    public void decryptFile(SecretKey secretKey, IvParameterSpec iv, String srcFile, String destFile) {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            try (FileInputStream fis = new FileInputStream(srcFile);
                 CipherInputStream cis = new CipherInputStream(fis, cipher);
                 FileOutputStream fos = new FileOutputStream(destFile)) {
                byte[] buffer = new byte[1024];
                int read;
                while ((read = cis.read(buffer)) != -1) {
                    fos.write(buffer, 0, read);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

效果如下:

image-20240731151628904

加密文件时会在指定位置生成加密的密钥和 IV (保持加密的一致性)

image-20240731151819543