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

Java AES加密解密报错IllegalBlockSizeException问题求助

问题分析与修复方案

直接错误原因

你遇到的IllegalBlockSizeException是因为加密后的字节数组直接转成String会破坏原始字节序列:AES加密输出的字节包含大量非UTF-8兼容的字符,直接用new String(encryptedBytes)转换时,JVM会用默认编码(通常是UTF-8)处理无法识别的字节,导致部分字节被替换或丢失,最终解密时输入的字节长度不再是16的倍数,触发块大小错误。

核心修复步骤

要安全存储加密结果,必须用Base64对加密字节进行编码,将二进制数据转换为可安全传输/存储的ASCII字符串。具体修改:

  1. 加密后:将encryptedBytes用Base64编码成字符串返回
  2. 解密前:先将输入的加密字符串解码回原始encryptedBytes

隐藏的密钥不一致问题

你的代码还有一个致命问题:generatePBEKeySpec方法每次调用都会生成新的随机盐,导致加密和解密时生成的AES密钥完全不同——即使密码正确,解密也会失败。必须将盐与加密结果一起存储,解密时复用同一个盐。

完整修复后的代码

SecurityManager.java

package de.example.org;

import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

public class SecurityManager {
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
    private static final int SALT_LENGTH = 16;
    private static final int ITERATION_COUNT = 65536;
    private static final int KEY_LENGTH = 128;

    private static final SecureRandom secureRandom = new SecureRandom();
    private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder();
    private static final Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    private static final SecretKeyFactory factory;

    static {
        try {
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    // 生成盐(单独提取,方便复用)
    private static byte[] generateSalt() {
        byte[] salt = new byte[SALT_LENGTH];
        secureRandom.nextBytes(salt);
        return salt;
    }

    private static PBEKeySpec generatePBEKeySpec(char[] password, byte[] salt) {
        return new PBEKeySpec(password, salt, ITERATION_COUNT, KEY_LENGTH);
    }

    public static String generateRandomString(int length) {
        byte[] randomBytes = new byte[length];
        secureRandom.nextBytes(randomBytes);
        return base64Encoder.encodeToString(randomBytes);
    }

    // 加密方法:返回 "盐:加密内容" 的Base64拼接字符串
    public static String encrypt(String plainText, String password) throws Exception {
        // 生成盐
        byte[] salt = generateSalt();
        // 生成AES密钥
        SecretKey secretKey = factory.generateSecret(generatePBEKeySpec(password.toCharArray(), salt));
        Key secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), ALGORITHM);
        // 加密明文
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        // 将盐和加密内容都转成Base64,用冒号分隔后返回
        String saltBase64 = base64Encoder.encodeToString(salt);
        String encryptedBase64 = base64Encoder.encodeToString(encryptedBytes);
        return saltBase64 + ":" + encryptedBase64;
    }

    public static String decrypt(String encryptedData, String password) throws Exception {
        // 拆分盐和加密内容
        String[] parts = encryptedData.split(":", 2);
        if (parts.length != 2) {
            throw new IllegalArgumentException("无效的加密数据格式");
        }
        byte[] salt = base64Decoder.decode(parts[0]);
        byte[] encryptedBytes = base64Decoder.decode(parts[1]);
        // 用同一个盐生成密钥
        SecretKey secretKey = factory.generateSecret(generatePBEKeySpec(password.toCharArray(), salt));
        Key secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), ALGORITHM);
        // 解密
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes);
    }
}

Main类调用示例

String plainText = SecurityManager.generateRandomString(128);
System.out.println("原始字符串: " + plainText);
String encrypted = SecurityManager.encrypt(plainText, "123");
System.out.println("加密后字符串: " + encrypted);
String decrypted = SecurityManager.decrypt(encrypted, "123");
System.out.println("解密后字符串: " + decrypted);
System.out.println("解密是否匹配: " + plainText.equals(decrypted));

额外注意事项

  • ECB模式的安全性:AES/ECB模式是不安全的,建议改用AES/CBC/PKCS5Padding或AES/GCM/NoPadding(GCM提供认证加密,更安全),但需要额外处理IV(初始化向量),逻辑类似盐的处理——IV也需要和加密结果一起存储。
  • 密码学算法选择:PBKDF2WithHmacSHA1已经不是最优选择,建议使用PBKDF2WithHmacSHA256或更高版本的哈希算法。
  • 字节编码一致性:在转换字符串和字节时,最好显式指定编码(如UTF-8),避免依赖系统默认编码导致跨平台问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 01:21:06