1. Overview
1.概述
In this tutorial, we’ll take a look on how to encrypt and decrypt a file using existing JDK APIs.
在本教程中,我们将看看如何使用现有的JDK API对文件进行加密和解密。
2. Writing a Test First
2.首先写一个测试
We’ll start by writing our test, TDD style. Since we’re going to work with files here, an integration test seems to be appropriate.
我们将从写我们的测试开始,TDD风格。由于我们将在这里与文件一起工作,一个集成测试似乎是合适的。
As we’re just using existing JDK functionality, no external dependencies are necessary.
由于我们只是在使用现有的JDK功能,所以不需要外部依赖性。
First, we’ll encrypt the content using a newly generated secret key (we’re using AES, Advanced Encryption Standard, as the symmetric encryption algorithm in this example).
首先,我们将使用新生成的秘密密钥对内容进行加密(我们使用AES,高级加密标准,作为本例中的对称加密算法)。
Also note, that we’re defining the complete transformation string in the constructor (AES/CBC/PKCS5Padding), which is a concatenation of used encryption, block cipher mode, and padding (algorithm/mode/padding). JDK implementations support a number of different transformations by default, but please note, that not every combination can still be considered cryptographically secure by today’s standards.
还要注意的是,我们在构造函数中定义了完整的转换字符串(AES/CBC/PKCS5Padding),它是所使用的加密、块密码模式和填充(algorithm/mode/padding)的连接。JDK实现默认支持许多不同的转换,但请注意,按照今天的标准,并不是每一种组合都能被视为加密安全。
We’ll assume our FileEncrypterDecrypter class will write the output to a file called baz.enc. Afterward, we decrypt this file using the same secret key and check that the decrypted content is equal to the original content:
我们将假设我们的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. Encryption
3.加密
We’ll initialize the cipher in the constructor of our FileEncrypterDecrypter class using the specified transformation String.
我们将在我们的FileEncrypterDecrypter类的构造函数中使用指定的转换String.初始化该密码。
This allows us to fail early in case a wrong transformation was specified:
这使我们能够在指定了错误的转换的情况下提前失败。
FileEncrypterDecrypter(SecretKey secretKey, String transformation) {
this.secretKey = secretKey;
this.cipher = Cipher.getInstance(transformation);
}
We can then use the instantiated cipher and the provided secret key to perform the encryption:
然后我们可以使用实例化的密码和提供的秘密密钥来执行加密:。
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 allows us to leverage the convenient CipherOutputStream class for writing the encrypted content into another OutputStream.
Java允许我们利用方便的CipherOutputStream类,将加密的内容写入另一个OutputStream。
Please note that we’re writing the IV (Initialization Vector) to the beginning of the output file. In this example, the IV is automatically generated when initializing the Cipher.
请注意,我们要把IV(初始化向量)写到输出文件的开头。在这个例子中,IV是在初始化Cipher时自动生成的。
Using an IV is mandatory when using CBC mode, in order to randomize the encrypted output. The IV is however not considered a secret, so it’s okay to write it at the beginning of the file.
在使用CBC模式时,使用IV是必须的,以使加密的输出随机化。然而,IV不被认为是一个秘密,所以把它写在文件的开头也可以。
4. Decryption
4.解密
For decrypting we likewise have to read the IV first. Afterward, we can initialize our cipher and decrypt the content.
对于解密,我们同样要先读取IV。之后,我们可以初始化我们的密码并解密内容。
Again we can make use of a special Java class, CipherInputStream, which transparently takes care of the actual decryption:
我们可以再次利用一个特殊的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. Conclusion
5.总结
We’ve seen we can perform basic encryption and decryption using standard JDK classes, such as Cipher, CipherOutputStream and CipherInputStream.
我们已经看到我们可以使用标准的JDK类来执行基本的加密和解密,例如Cipher、CipherOutputStream和CipherInputStream。
As usual, the complete code for this article is available in our GitHub repository.
像往常一样,本文的完整代码可在我们的GitHub 仓库中找到。
In addition, you can find a list of the Ciphers available in the JDK here.
此外,你可以找到JDK中可用的密码器的列表这里。
Finally, do note that the code examples here aren’t meant as production-grade code and the specifics of your system need to be considered thoroughly when using them.
最后,请注意,这里的代码例子并不意味着是生产级的代码,在使用它们时,需要彻底考虑你的系统的具体情况。