文件流加密涉及到大文件加密过程,不能直接使用Cipher.doFinal(byte[] bytes)方法进行直接加密
超大文件会导致内存溢出。
解决方法:
可以使用 Cipher.update(byte[] bytes) 方法进行文件流部分加密数据,
当整个文件流数据都加密完后,使用 Cipher.doFinal()方法来生成填充内容,保证最后一段内容也是完整128位数据块
所以会使用CipherInputStream 或者 CipherOutputStream进行文件加解密
文件流加密时,会使用CipherOutputStream来包装输出流,当调用outputStream.close()方法时,会执行cipher.doFinal()方法生成最后一个byte[] 数据,并写出到输出流,flush()才算加密完成。
文件解密时,会使用 CipherInputStream 来包装输入流, 当读取到inputStream.read()最后内容时,会执行cipher.doFinal()方法生成的byte[] 也要写出到输出流才可以。
说下原理:
CipherInputStream
对输入流进行封装
CipherInputStream.read()读取字节流时调用的cipher.update()方法进行流部分加密, 当加密到最后一段时,会调用 doFinal() 方法。
CipherOutputStream
对输出流进行封装,当要写入固定字节数据时,先加密,再写出
CipherOutputStream.write() 中调用 cipher.update() 方法进行字节数组加密后写出
再CipherOutputStream.close()中调用cipher.doFinal()方法 填充最后一段内容
贴个示例代码
public static void aesEncryptFile(String sourceFilePath, String destFilePath, String key) throws Exception {
aesFile(sourceFilePath, destFilePath, key, Cipher.ENCRYPT_MODE);
}
public static void aesDecryptFile(String sourceFilePath, String destFilePath, String key) throws Exception {
aesFile(sourceFilePath, destFilePath, key, Cipher.DECRYPT_MODE);
}
public static void aesEncryptFileForInput(String sourceFilePath, String destFilePath, String key) throws Exception {
aesFileForInput(sourceFilePath, destFilePath, key, Cipher.ENCRYPT_MODE);
}
public static void aesDecryptFileForInput(String sourceFilePath, String destFilePath, String key) throws Exception {
aesFileForInput(sourceFilePath, destFilePath, key, Cipher.DECRYPT_MODE);
}
/**
* 通过文件输入流加密文件并输出到指定路径
* CipherOutputStream进行加密数据
*/
public static void aesFile(String sourceFilePath, String destFilePath, String key, int mode) throws Exception {
File sourceFile = new File(sourceFilePath);
File destFile = new File(destFilePath);
if (!sourceFile.exists() && !sourceFile.isFile()) {
throw new IllegalArgumentException("加密源文件不存在");
}
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(destFile);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(mode, secretKeySpec);
// 对输出流包装
CipherOutputStream cout = new CipherOutputStream(out, cipher);
byte[] cache = new byte[1024];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
cout.write(cache, 0, nRead);
cout.flush();
}
cout.close();
out.close();
in.close();
}
/**
* 通过文件输入流加密文件并输出到指定路径
* CipherInputStream进行加密数据
*/
public static void aesFileForInput(String sourceFilePath, String destFilePath, String key, int mode) throws Exception {
File sourceFile = new File(sourceFilePath);
File destFile = new File(destFilePath);
if (!sourceFile.exists() && !sourceFile.isFile()) {
throw new IllegalArgumentException("加密源文件不存在");
}
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(destFile);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(mode, secretKeySpec);
// 对输入流包装
CipherInputStream cin = new CipherInputStream(in, cipher);
byte[] cache = new byte[1024];
int nRead = 0;
while ((nRead = cin.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
cin.close();
in.close();
}