文件流加密涉及到大文件加密過程,不能直接使用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(); }