You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 22:14:55