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

AES加解密工具类执行失败,抛出BadPaddingException问题排查

问题描述

我实现了一个用于AES加解密字符串的工具类CryptoHelper,代码参考了AES加密示例。

工具类代码如下:

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoHelper {

    private static final String CIPHER_ALGORITM_NAME = "AES/CBC/PKCS5Padding";
    private static final String HASHING_ALGO_NAME = "PBKDF2WithHmacSHA1";
    private static final int KEY_TARGET_LENGTH = 256;
    private static final int HASHING_ITERATIONS = 65536;

    public static SecretKey getSecretKey(String string, byte[] salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        KeySpec spec = new PBEKeySpec(string.toCharArray(), salt, HASHING_ITERATIONS, KEY_TARGET_LENGTH);

        try {
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(HASHING_ALGO_NAME);
            SecretKey encryptedPassword = new SecretKeySpec(keyFactory.generateSecret(spec).getEncoded(), "AES");
            return encryptedPassword;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw e;
        }

    }

    public static IvParameterSpec getInitializationVector() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        return new IvParameterSpec(iv);
    }

    public static String encrypt(String input, String password, byte[] salt)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM_NAME);

        SecretKey secretKey = null;
        try {
            secretKey = getSecretKey(password, salt);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            // code
        }
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, getInitializationVector());
        byte[] cipherText = cipher.doFinal(input.getBytes());
        return Base64.getEncoder().encodeToString(cipherText);
    }

    public static String decrypt(String encryptedText, String password, byte[] salt)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM_NAME);
        SecretKey secretKey = null;
        try {
            secretKey = getSecretKey(password, salt);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            // code
        }
        cipher.init(Cipher.DECRYPT_MODE, secretKey, getInitializationVector());
        byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        return new String(plainText);
    }
}

编写单元测试验证时,抛出了以下异常:

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

测试代码如下:

@Test
public void testDecrypt() {
    String encryptedString = "";
    String password = "password";

    try {
        encryptedString = CryptoHelper.encrypt("some string", password, password.getBytes());
    } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException
            | InvalidAlgorithmParameterException | BadPaddingException | IllegalBlockSizeException e) {
        e.printStackTrace();
        assertNull(e);
    }

    try {
        CryptoHelper.decrypt(encryptedString, password, password.getBytes());
    } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException
            | InvalidAlgorithmParameterException | BadPaddingException | IllegalBlockSizeException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

请问问题出在哪里?


问题原因及解决方案

核心问题:加密和解密使用了不同的初始化向量(IV)

你当前的代码中,encrypt和decrypt方法各自调用getInitializationVector()生成全新的随机IV。但AES/CBC模式要求加密和解密必须使用完全相同的IV,否则无法正确解密,就会抛出BadPaddingException。

IV本身不需要保密,但必须和密文一起保存,才能在解密时复用。

修复方案

修改工具类,将加密生成的IV和密文拼接在一起(比如先存IV的Base64编码,再存密文的Base64编码,用分隔符分开),解密时再拆分出IV和密文:

  1. 修改encrypt方法,返回IV+密文的组合字符串:
public static String encrypt(String input, String password, byte[] salt)
        throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
        InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeySpecException {

    Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM_NAME);
    SecretKey secretKey = getSecretKey(password, salt);
    IvParameterSpec ivSpec = getInitializationVector();
    
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
    byte[] cipherText = cipher.doFinal(input.getBytes());
    
    // 将IV和密文分别Base64编码后用冒号分隔
    String ivBase64 = Base64.getEncoder().encodeToString(ivSpec.getIV());
    String cipherBase64 = Base64.getEncoder().encodeToString(cipherText);
    return ivBase64 + ":" + cipherBase64;
}
  1. 修改decrypt方法,先拆分出IV和密文再解密:
public static String decrypt(String encryptedCombined, String password, byte[] salt)
        throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
        InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeySpecException {

    // 拆分IV和密文
    String[] parts = encryptedCombined.split(":", 2);
    if (parts.length != 2) {
        throw new IllegalArgumentException("无效的加密字符串格式");
    }
    
    byte[] iv = Base64.getDecoder().decode(parts[0]);
    byte[] cipherText = Base64.getDecoder().decode(parts[1]);
    
    Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM_NAME);
    SecretKey secretKey = getSecretKey(password, salt);
    IvParameterSpec ivSpec = new IvParameterSpec(iv);
    
    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
    byte[] plainText = cipher.doFinal(cipherText);
    return new String(plainText);
}
  1. 额外修复:原代码中encrypt和decrypt方法里捕获getSecretKey的异常后直接忽略,会导致secretKey为null,后续初始化Cipher时也会报错,需要去掉这部分无效的异常捕获,让异常正常向上抛出。

额外优化点

  • 不要用password.getBytes()作为salt,salt应该随机生成,并且和IV、密文一起保存,否则会降低密钥推导的安全性。
  • 可以考虑使用更安全的哈希算法,比如PBKDF2WithHmacSHA256替代PBKDF2WithHmacSHA1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 17:05:32