在Java中加密和解密文件

2023/07/02

1. 概述

在本教程中,我们将介绍如何使用现有的JDK API加密和解密文件。

2. 先编写测试

我们将从编写测试、TDD风格开始。由于我们将在这里处理文件,因此集成测试似乎是合适的。

由于我们只是使用现有的JDK功能,因此不需要外部依赖项。

首先,我们将使用新生成的密钥对内容进行加密(在本例中使用AES(高级加密标准)作为对称加密算法)。

另请注意,我们在构造函数(AES/CBC/PKCS5Padding)中定义了完整的转换字符串,它是已用加密、分组密码模式和填充(algorithm/mode/padding)的串联。默认情况下,JDK实现支持许多不同的转换,但请注意,并非每个组合仍然可以按照今天的标准被认为是加密安全的。

我们假设我们的FileEncrypterDecrypter类会将输出写入名为baz.enc的文件。之后,我们使用相同的密钥解密此文件,并检查解密后的内容是否与原始内容相同:

@Test
public void whenEncryptingIntoFile_andDecryptingFileAgain_thenOriginalStringIsReturned() {
    String originalContent = "foobar";
    SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();

    FileEncrypterDecrypter fileEncrypterDecrypter = new FileEncrypterDecrypter(secretKey, "AES/CBC/PKCS5Padding");
    fileEncrypterDecrypter.encrypt(originalContent, "baz.enc");

    String decryptedContent = fileEncrypterDecrypter.decrypt("baz.enc");
    assertThat(decryptedContent, is(originalContent));

    new File("baz.enc").delete(); // cleanup
}

3. 加密

我们将使用指定的转换字符串在FileEncrypterDecrypter类的构造函数中初始化Cipher。

这允许我们在指定错误转换的情况下尽早失败:

FileEncrypterDecrypter(SecretKey secretKey, String transformation) {
    this.secretKey = secretKey;
    this.cipher = Cipher.getInstance(transformation);
}

然后我们可以使用实例化的Cipher和提供的密钥来执行加密

void encrypt(String content, String fileName) {
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    byte[] iv = cipher.getIV();

    try (FileOutputStream fileOut = new FileOutputStream(fileName); 
        CipherOutputStream cipherOut = new CipherOutputStream(fileOut, cipher)) {
        fileOut.write(iv);
        cipherOut.write(content.getBytes());
    }
}

Java允许我们利用方便的CipherOutputStream类将加密内容写入另一个OutputStream

请注意,我们将IV(初始化向量)写入输出文件的开头。在此示例中,IV是在初始化Cipher时自动生成的。

使用CBC模式时必须使用IV,以便随机化加密输出。然而,IV不被视为机密,因此可以将其写在文件的开头。

4. 解密

为了解密,我们同样必须先读取IV。之后,我们可以初始化Cipher并解密内容。

我们可以再次使用一个特殊的Java类CipherInputStream,它透明地处理实际的解密

String decrypt(String fileName) {
    String content;

    try (FileInputStream fileIn = new FileInputStream(fileName)) {
        byte[] fileIv = new byte[16];
        fileIn.read(fileIv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv));

        try (CipherInputStream cipherIn = new CipherInputStream(fileIn, cipher);
                InputStreamReader inputReader = new InputStreamReader(cipherIn);
                BufferedReader reader = new BufferedReader(inputReader)
            ) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            content = sb.toString();
        }

    }
    return content;
}

5. 总结

我们已经看到,我们可以使用标准JDK类(例如Cipher、CipherOutputStream和CipherInputStream)执行基本的加密和解密。

与往常一样,本教程的完整源代码可在GitHub上获得。

此外,你可以在此处找到JDK中可用的Cipher列表。

最后,请注意,此处的代码示例并不是生产级代码,在使用它们时需要全面考虑系统的具体情况。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章