AES-256/CBC/PKCS7PADDING解密D01文件时出现多余空值问题
AES-256/CBC/PKCS7Padding解密后文件出现多余空值的解决方案
我使用BouncyCastle库,采用AES-256/CBC/PKCS7PADDING算法对D01文件加密,解密后发现文件中出现多余空值(Null)。原D01文件内容无多余空值,解密后的文件末尾存在大量空值数据。
密钥值为:Qxyyywqg1UdBRpUV
相关代码实现
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.security.SecureRandom; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.BlockCipherPadding; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; import org.springframework.stereotype.Component; @Component public class CryptoUtilsV2 { public static byte[] iv = generateIV(); public int encryptDCRenewal(String keyText, File inputFile, File outputFile) throws Exception { //inputFile is d01 file and outfile is the d01 file to which encrypted data is written // initialize key and iv byte[] secretKey = keyText.getBytes(StandardCharsets.UTF_8); FileInputStream inputStream = new FileInputStream(inputFile); byte[] inputBytes = new byte[(int) inputFile.length()]; inputStream.read(inputBytes); inputStream.close(); // Encrypt the message byte[] ciphertext = encrypt(inputBytes, secretKey, iv); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(ciphertext); outputStream.close(); return 0; } public int decryptDCRenewal(String keyText, File inputFile, File outputFile) throws Exception { // initialize key and iv //inputFile is encrypted d01 file and outfile is the do1 file to which decrypted data is written byte[] secretKey = keyText.getBytes(StandardCharsets.UTF_8); FileInputStream inputStream = new FileInputStream(inputFile); byte[] inputBytes = new byte[(int) inputFile.length()]; inputStream.read(inputBytes); inputStream.close(); // Encrypt the message byte[] decryptedText = decrypt(inputBytes, secretKey, iv); FileOutputStream outputStream = new FileOutputStream(outputFile); outputStream.write(decryptedText); outputStream.close(); return 0; } private static byte[] decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws Exception { BufferedBlockCipher cipher = createCipher(false, key, iv); byte[] output = new byte[cipher.getOutputSize(ciphertext.length)]; int bytesWritten = cipher.processBytes(ciphertext, 0, ciphertext.length, output, 0); bytesWritten += cipher.doFinal(output, bytesWritten); return output; } private static byte[] encrypt(byte[] input, byte[] key, byte[] iv) throws Exception { BufferedBlockCipher cipher = createCipher(true, key, iv); byte[] output = new byte[cipher.getOutputSize(input.length)]; int bytesWritten = cipher.processBytes(input, 0, input.length, output, 0); bytesWritten += cipher.doFinal(output, bytesWritten); return output; } private static BufferedBlockCipher createCipher(boolean forEncryption, byte[] key, byte[] iv) { BlockCipherPadding padding = new PKCS7Padding(); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESEngine()), padding); CipherParameters params = new ParametersWithIV(new KeyParameter(key), iv); cipher.init(forEncryption, params); return cipher; } private static byte[] generateIV() { byte[] iv = new byte[16]; // AES 256 use 16 byte IV new SecureRandom().nextBytes(iv); return iv; } }
核心问题原因
解密方法中直接返回了初始化的完整output数组,但cipher.getOutputSize()返回的是最大可能输出长度(包含padding预留空间),而实际解密后有效数据长度是bytesWritten变量的值(PKCS7Padding会自动去除加密时添加的padding)。返回整个数组会把未使用的0字节(空值)写入文件,导致出现多余空值。
解决方案
1. 修正解密/加密方法,返回有效长度的字节数组
修改decrypt和encrypt方法,只保留有效长度的字节内容:
private static byte[] decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws Exception { BufferedBlockCipher cipher = createCipher(false, key, iv); byte[] output = new byte[cipher.getOutputSize(ciphertext.length)]; int bytesWritten = cipher.processBytes(ciphertext, 0, ciphertext.length, output, 0); bytesWritten += cipher.doFinal(output, bytesWritten); // 截取有效长度的字节数组 return java.util.Arrays.copyOf(output, bytesWritten); } private static byte[] encrypt(byte[] input, byte[] key, byte[] iv) throws Exception { BufferedBlockCipher cipher = createCipher(true, key, iv); byte[] output = new byte[cipher.getOutputSize(input.length)]; int bytesWritten = cipher.processBytes(input, 0, input.length, output, 0); bytesWritten += cipher.doFinal(output, bytesWritten); // 加密也返回有效长度数组,避免密文末尾多余0 return java.util.Arrays.copyOf(output, bytesWritten); }
2. 修复其他潜在安全/可靠性问题
- IV复用问题:当前
iv是静态变量,程序启动后只生成一次,违反AES/CBC安全规范(每次加密必须使用新随机IV)。正确做法是每次加密生成新IV,并将IV写入加密文件开头,解密时先读取IV再解密:// 修改加密方法,写入IV+密文 public int encryptDCRenewal(String keyText, File inputFile, File outputFile) throws Exception { byte[] secretKey = keyText.getBytes(StandardCharsets.UTF_8); byte[] inputBytes = Files.readAllBytes(inputFile.toPath()); // 更可靠的文件读取 byte[] iv = generateIV(); // 每次加密生成新IV byte[] ciphertext = encrypt(inputBytes, secretKey, iv); try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { outputStream.write(iv); outputStream.write(ciphertext); } return 0; } // 修改解密方法,先读取IV public int decryptDCRenewal(String keyText, File inputFile, File outputFile) throws Exception { byte[] secretKey = keyText.getBytes(StandardCharsets.UTF_8); byte[] fileBytes = Files.readAllBytes(inputFile.toPath()); // 读取开头16字节IV byte[] iv = new byte[16]; System.arraycopy(fileBytes, 0, iv, 0, 16); // 剩余部分为密文 byte[] ciphertext = new byte[fileBytes.length - 16]; System.arraycopy(fileBytes, 16, ciphertext, 0, ciphertext.length); byte[] decryptedText = decrypt(ciphertext, secretKey, iv); try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { outputStream.write(decryptedText); } return 0; } - 密钥长度问题:给定密钥
Qxyyywqg1UdBRpUVUTF-8编码后是16字节,对应AES-128,并非AES-256(需要32字节密钥)。如需使用AES-256,需更换为32字节长度的密钥。 - 文件读取可靠性:原代码
inputStream.read(inputBytes)可能无法一次性读取完整文件,改用Files.readAllBytes更稳定。
内容的提问来源于stack exchange,提问作者Thejus32
相关产品推荐
相关产品推荐

