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

Java Cipher调用doFinal时抛出IllegalBlockSizeException问题求助

解决加密解密时的IllegalBlockSizeException问题

嘿,我来帮你搞定这个加密解密的头疼问题!你遇到的IllegalBlockSizeException提示“Input length must be multiple of 16 when decrypting with padded cipher”,这在对称加密(比如AES)里是非常常见的问题,咱们一步步拆解原因,然后给出可行的解决方案。

为什么会出现这个异常?

你用的应该是块加密算法(比如AES),这类算法的默认模式通常是带填充的(比如PKCS5Padding)。简单来说:

  • 加密时,算法会自动把原始数据填充到16字节的整数倍(AES的块大小是16字节)
  • 解密时,要求输入的密文必须严格是16字节的整数倍,否则就会抛出这个异常

常见的触发原因有这几个:

  • 密文在传输/转换过程中被截断、篡改,或者用了错误的编码(比如直接把字节数组转成普通字符串,导致字节丢失)
  • 你的单例类里把Cipher作为成员变量复用,而Cipher是有状态的,重复使用会导致状态混乱
  • 加密和解密时用的算法模式/填充规则不一致
  • 密钥长度不符合算法要求(不过这个通常会抛出另一种异常,先放在后面排查)

针对性解决方案

1. 修复单例类的Cipher使用方式

绝对不要把Cipher作为单例类的成员变量! 因为Cipher不是线程安全的,而且每次调用doFinal()后状态会变化,复用很容易导致解密时的块大小错误。正确的做法是每次加密/解密时重新初始化Cipher。

修改你的SecurityHandler示例(我帮你补全了完整的加密解密逻辑,包括CBC模式的IV处理):

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

public class SecurityHandler {
    private static SecurityHandler instance;
    private final String securityKey;

    // 私有构造方法,初始化密钥
    private SecurityHandler() {
        // 注意:密钥的UTF-8编码长度必须是16/24/32字节(对应AES 128/192/256位)
        securityKey = "adnanyc83z43h1jbncxgsdfgs..."; 
    }

    // 单例获取方法
    public static synchronized SecurityHandler getInstance() {
        if (instance == null) {
            instance = new SecurityHandler();
        }
        return instance;
    }

    // 加密方法:返回包含IV的字节数组(解密需要用同一个IV)
    public byte[] encrypt(String plainText) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(securityKey.getBytes(StandardCharsets.UTF_8), "AES");
        // 明确指定算法模式和填充,不要依赖JVM默认值
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        
        // CBC模式需要随机IV,这里生成一个和块大小一致的IV
        byte[] iv = new byte[cipher.getBlockSize()];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        
        // 把IV和密文拼接在一起返回,解密时先提取IV
        byte[] result = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, result, iv.length, encryptedBytes.length);
        return result;
    }

    // 解密方法:传入包含IV的加密数据
    public String decrypt(byte[] encryptedData) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(securityKey.getBytes(StandardCharsets.UTF_8), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        
        // 先从加密数据中提取IV
        byte[] iv = new byte[cipher.getBlockSize()];
        System.arraycopy(encryptedData, 0, iv, 0, iv.length);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        
        // 提取真正的密文部分
        byte[] encryptedBytes = new byte[encryptedData.length - iv.length];
        System.arraycopy(encryptedData, iv.length, encryptedBytes, 0, encryptedBytes.length);
        
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }

    // 可选:如果需要把密文转成字符串传输,用Base64编码(避免字节丢失)
    public String encryptToBase64(String plainText) throws Exception {
        byte[] encryptedData = encrypt(plainText);
        return Base64.getEncoder().encodeToString(encryptedData);
    }

    // 可选:从Base64字符串解密
    public String decryptFromBase64(String encryptedBase64) throws Exception {
        byte[] encryptedData = Base64.getDecoder().decode(encryptedBase64);
        return decrypt(encryptedData);
    }
}

2. 避免密文的错误编码

如果需要把密文转换成字符串(比如通过HTTP接口传输),绝对不要直接用new String(encryptedBytes)!因为加密后的字节数组可能包含非UTF-8兼容的字节,直接转字符串会导致数据丢失或损坏,进而解密时出现块大小错误。

正确的做法是用Base64编码把字节数组转成字符串,就像上面示例里的encryptToBase64和decryptFromBase64方法那样。

3. 确保加密和解密的模式/填充完全一致

一定要明确指定Cipher的完整算法字符串,比如AES/CBC/PKCS5Padding,不要只写AES。因为不同JVM的默认模式可能不一样(比如有些默认是ECB,有些是CBC),一旦加密和解密的模式/填充不匹配,就会抛出块大小错误或者解密失败。

4. 检查密钥长度

AES要求密钥长度必须是16、24或32字节(对应128、192、256位)。你要确认你的securityKey字符串用UTF-8编码后的字节长度符合这个要求,如果不符合,初始化SecretKeySpec时会有问题(虽然报错可能不是当前这个,但也是潜在隐患)。

额外提醒

  • Cipher不是线程安全的:所以绝对不能把它作为单例成员变量,多个线程同时调用会导致状态混乱,出现各种奇怪的异常。
  • CBC模式必须使用IV:IV应该是随机生成的,而且解密时必须用和加密时相同的IV,所以要把IV和密文一起保存或传输。
  • 不要硬编码密钥:示例里是为了演示,生产环境绝对不能硬编码密钥,要从安全的配置中心或密钥管理服务获取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:12:39