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

如何修复javax.crypto.BadPaddingException: 给定最终块未正确填充

修复AES解密时的BadPaddingException异常

问题背景

我用Java实现密码保险箱,编写了EncryptionModule类,包含encrypt(String password, String databaseName)和decrypt(String password, String databaseName)方法,支持CSV文件加解密。执行解密时抛出异常:

Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

此前曾遇到java.security.InvalidKeyException: Invalid AES key length异常,修改代码后出现当前问题。

核心问题分析

当前代码的致命错误是解密流程错误地重新生成了salt和IV:

  • 加密时,代码生成随机的salt和IV,将IV(16字节) + salt(16字节) + 密文写入加密文件;
  • 但解密时,代码没有从加密文件中读取已存储的salt和IV,而是重新生成了新的随机值,导致用错误的参数推导密钥,最终解密失败抛出BadPaddingException。

修复方案

拆分加密和解密逻辑,确保解密时使用加密过程中生成并存储的salt和IV:

修改后的EncryptionModule代码

package com.example.passwordsafe.data;

import com.example.passwordsafe.core.usecases.EncryptionModuleInterface;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

public class EncryptionModule implements EncryptionModuleInterface {
    private static final String PBKDF_ALGORITHM = "PBKDF2WithHmacSHA1";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";
    private static final int SALT_LENGTH = 16;
    private static final int IV_LENGTH = 16;
    private static final int ITERATION_COUNT = 1000000;
    private static final int KEY_LENGTH = 256;

    @Override
    public void encrypt(String password, String databaseName) {
        Path path = Paths.get(databaseName);
        if (Files.exists(path)) {
            File plaintextFile = new File(databaseName);
            File encryptedFile = new File(databaseName + ".encrypted");
            doEncrypt(password, plaintextFile, encryptedFile);
        } else {
            System.out.println("File does not exist");
        }
    }

    @Override
    public void decrypt(String password, String databaseName) {
        Path path = Paths.get(databaseName);
        if (Files.exists(path)) {
            File encryptedFile = new File(databaseName);
            File plaintextFile = new File(databaseName.replace(".encrypted", ".decrypted"));
            doDecrypt(password, encryptedFile, plaintextFile);
        } else {
            System.out.println("File does not exist");
        }
    }

    private void doEncrypt(String password, File inputFile, File outputFile) {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        byte[] ivBytes = new byte[IV_LENGTH];
        random.nextBytes(ivBytes);

        try {
            // 推导密钥
            SecretKeySpec keySpec = deriveKey(password, salt);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);

            // 初始化Cipher
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

            // 读取明文并加密
            FileInputStream inputStream = new FileInputStream(inputFile);
            byte[] inputBytes = new byte[(int) inputFile.length()];
            inputStream.read(inputBytes);
            byte[] encValue = cipher.doFinal(inputBytes);

            // 组装输出数据:IV + salt + 密文
            byte[] finalOutput = new byte[IV_LENGTH + SALT_LENGTH + encValue.length];
            System.arraycopy(ivBytes, 0, finalOutput, 0, IV_LENGTH);
            System.arraycopy(salt, 0, finalOutput, IV_LENGTH, SALT_LENGTH);
            System.arraycopy(encValue, 0, finalOutput, IV_LENGTH + SALT_LENGTH, encValue.length);

            // 写入加密文件
            FileOutputStream outputStream = new FileOutputStream(outputFile);
            outputStream.write(finalOutput);

            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void doDecrypt(String password, File inputFile, File outputFile) {
        try {
            FileInputStream inputStream = new FileInputStream(inputFile);
            byte[] inputBytes = new byte[(int) inputFile.length()];
            inputStream.read(inputBytes);

            // 从加密文件中读取IV和salt
            byte[] ivBytes = new byte[IV_LENGTH];
            System.arraycopy(inputBytes, 0, ivBytes, 0, IV_LENGTH);
            byte[] salt = new byte[SALT_LENGTH];
            System.arraycopy(inputBytes, IV_LENGTH, salt, 0, SALT_LENGTH);
            // 剩余部分是密文
            byte[] encValue = new byte[inputBytes.length - IV_LENGTH - SALT_LENGTH];
            System.arraycopy(inputBytes, IV_LENGTH + SALT_LENGTH, encValue, 0, encValue.length);

            // 推导密钥(使用加密时的salt)
            SecretKeySpec keySpec = deriveKey(password, salt);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);

            // 初始化Cipher并解密
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
            byte[] decValue = cipher.doFinal(encValue);

            // 写入明文文件
            FileOutputStream outputStream = new FileOutputStream(outputFile);
            outputStream.write(decValue);

            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 提取密钥推导逻辑为公共方法,确保加密解密使用相同逻辑
    private SecretKeySpec deriveKey(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALGORITHM);
        byte[] key = factory.generateSecret(spec).getEncoded();
        return new SecretKeySpec(key, ALGORITHM);
    }
}

额外优化点

  1. 将密钥推导逻辑提取为deriveKey方法,避免重复代码,确保加密解密使用完全一致的密钥生成逻辑;
  2. 解密时调整输出文件名,用replace(".encrypted", ".decrypted")替代直接拼接,避免生成passwords.csv.encrypted.decrypted这类冗余文件名;
  3. 定义常量存储固定长度和迭代次数,提升代码可读性和可维护性。

内容的提问来源于stack exchange,提问作者max23

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:54:58