为了避免“内存不足错误”,您希望以 1 mb 大小的块解密一个大(加密)文件 - 是的,可以使用 AES CBC 模式。
您可以在下面找到一个完整的示例,该示例生成一个示例纯文本文件 ('plaintext.dat'),其中包含大小为 50 mb + 1 字节的随机内容(+ 1 字节可以很好地测试文件大小不是16 的精确倍数 = AES 块大小)。
在下一步中,该文件将使用随机创建的初始化向量和密钥加密为“ciphertext.dat”。
最后一步是请求的解密方法 - 它以 1 mb 的块解密加密文件,并在“// obuf 保存解密的块,对数据做你想做的事情”和“// 最终data' 你确实在字节数组 obuf 中有解密的数据。为了测试,我以附加模式将解密的数据写入文件“decryptedtext.dat”(因此,如果该文件存在,则在开头删除该文件)。
为了证明解密成功,我比较了明文和解密文本文件的 SHA256 哈希。
两个注意事项:我为 AES CBC 256 使用 32 字节 = 256 位长密钥。该程序没有适当的异常处理,仅用于教育目的。
结果:
decrypt AES CBC 256 in 1 mb chunks
file with random data created: plaintext.dat
encryption to ciphertext.dat was successfull: true
decryption in chunks of 1 mb
decrypted file written to decryptedtext.dat
plaintext equals decrytedtext file: true
代码:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.security.*;
import java.util.Arrays;
public class AES_CBC_chunk_decryption {
public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/63325528/decrypt-in-chunks-a-aes-128-cbc-encrypted-object/63325529#63325529");
System.out.println("decrypt AES CBC 256 in 1 mb chunks");
// setup for creation of a 50mb encrypted file
int filesize = (50 * 1024 * 1024) + 1; // 50 mb + 1 byte = 52428801 bytes
String filenamePlaintext = "plaintext.dat";
String filenameCiphertext = "ciphertext.dat";
String filenameDecryptedtext = "decryptedtext.dat";
File file = new File("plaintext.dat");
// fill with random bytes.
try (FileOutputStream out = new FileOutputStream(file)) {
byte[] bytes = new byte[filesize];
new SecureRandom().nextBytes(bytes);
out.write(bytes);
}
System.out.println("\nfile with random data created: " + filenamePlaintext);
// delete decrypted file if it exists
Files.deleteIfExists(new File(filenameDecryptedtext).toPath());
// setup random key & iv
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
byte[] key = new byte[32]; // I'm using a 32 byte = 256 bit long key for aes 256
secureRandom.nextBytes(iv);
secureRandom.nextBytes(key);
// encrypt complete file
boolean resultEncryption = encryptCbcFileBufferedCipherOutputStream(filenamePlaintext, filenameCiphertext, key, iv);
System.out.println("encryption to " + filenameCiphertext + " was successfull: " + resultEncryption);
// encrypted file is 52428816 bytes long
System.out.println("\ndecryption in chunks of 1 mb");
// decryption in chunks of 1 mb
try (FileInputStream in = new FileInputStream(filenameCiphertext)) {
byte[] ibuf = new byte[(1024 * 1024)]; // chunks of 1 mb
int len;
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
while ((len = in.read(ibuf)) != -1) {
byte[] obuf = cipher.update(ibuf, 0, len);
if (obuf != null)
// obuf holds the decrypted chunk, do what you want to do with the data
// I'm writing it to a file in appending mode
try (FileOutputStream output = new FileOutputStream(filenameDecryptedtext, true)) {
output.write(obuf);
}
}
byte[] obuf = cipher.doFinal();
if (obuf != null)
// final data
try (FileOutputStream output = new FileOutputStream(filenameDecryptedtext, true)) {
output.write(obuf);
}
}
System.out.println("decrypted file written to " + filenameDecryptedtext);
System.out.println("plaintext equals decrytedtext file: " + filecompareSha256Large(filenamePlaintext, filenameDecryptedtext));
}
public static boolean encryptCbcFileBufferedCipherOutputStream(String inputFilename, String outputFilename, byte[] key, byte[] iv)
throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
try (FileInputStream in = new FileInputStream(inputFilename);
FileOutputStream out = new FileOutputStream(outputFilename);
CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] buffer = new byte[8096];
int nread;
while ((nread = in.read(buffer)) > 0) {
encryptedOutputStream.write(buffer, 0, nread);
}
encryptedOutputStream.flush();
}
if (new File(outputFilename).exists()) {
return true;
} else {
return false;
}
}
public static boolean filecompareSha256Large(String filename1, String filename2) throws IOException, NoSuchAlgorithmException {
boolean result = false;
byte[] hash1 = generateSha256Buffered(filename1);
byte[] hash2 = generateSha256Buffered(filename2);
result = Arrays.equals(hash1, hash2);
return result;
}
private static byte[] generateSha256Buffered(String filenameString) throws IOException, NoSuchAlgorithmException {
// even for large files
byte[] buffer = new byte[8192];
int count;
MessageDigest md = MessageDigest.getInstance("SHA-256");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filenameString));
while ((count = bis.read(buffer)) > 0) {
md.update(buffer, 0, count);
}
bis.close();
return md.digest();
}
}