Java AES加密解密报错IllegalBlockSizeException问题求助
问题分析与修复方案
直接错误原因
你遇到的IllegalBlockSizeException是因为加密后的字节数组直接转成String会破坏原始字节序列:AES加密输出的字节包含大量非UTF-8兼容的字符,直接用new String(encryptedBytes)转换时,JVM会用默认编码(通常是UTF-8)处理无法识别的字节,导致部分字节被替换或丢失,最终解密时输入的字节长度不再是16的倍数,触发块大小错误。
核心修复步骤
要安全存储加密结果,必须用Base64对加密字节进行编码,将二进制数据转换为可安全传输/存储的ASCII字符串。具体修改:
- 加密后:将
encryptedBytes用Base64编码成字符串返回 - 解密前:先将输入的加密字符串解码回原始
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
相关产品推荐
相关产品推荐

