如何解决Spring Boot中解密时的IllegalBlockSizeException异常?
Spring Boot JPA AttributeConverter 加密解密异常排查
问题场景
在Spring Boot应用中使用AttributeConverter实现实体字段的持久化加密、查询解密,通过@Convert(converter = DataEncryptionConverter.class)标记目标字段。手动调用转换器的加密/解密方法、手动调用仓库存取值均正常,但执行部分JPA仓库方法(如User findByPrimaryEmail(String primaryEmail))时抛出IllegalBlockSizeException。
转换器代码实现
@Converter @NoArgsConstructor public class DataEncryptionConverter implements AttributeConverter<Object, String> { // @Value("${encryption.key}") private String encryptionKey = "secret-test-key1"; private final byte[] iv = new byte[16]; private final String encryptionCipher = "AES/CBC/PKCS5Padding"; private Key key; private Cipher cipher; private Key getKey() { if (key == null) { key = new SecretKeySpec(encryptionKey.getBytes(), "AES"); } return key; } private Cipher getCipher() throws GeneralSecurityException { if (cipher == null) { cipher = Cipher.getInstance(encryptionCipher); } return cipher; } // initilize cipher into the memory -- decides whether we want to encrypt or decrypt data with the cipher private void initCipher(int encryptMode) throws GeneralSecurityException { IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); getCipher().init(encryptMode, getKey(), ivParameterSpec); } @SneakyThrows @Override public String convertToDatabaseColumn(Object attribute) { if (attribute == null) { return null; } initCipher(Cipher.ENCRYPT_MODE); byte[] bytes = SerializationUtils.serialize(attribute); return Base64.getEncoder().encodeToString(getCipher().doFinal(bytes)); } @SneakyThrows @Override public Object convertToEntityAttribute(String dbData) { if (dbData == null) { return null; } initCipher(Cipher.DECRYPT_MODE); byte[] bytes = getCipher().doFinal(Base64.getDecoder().decode(dbData)); return SerializationUtils.deserialize(bytes); } }
抛出的异常
Apr 12, 2023 6:49:07 PM org.apache.catalina.core.StandardWrapperValve invoke SEVERE: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception java.lang.reflect.UndeclaredThrowableException at com.sun.proxy.$Proxy201.getSingleResult(Unknown Source) at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:196) at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:155) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:143) Caused by: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher at java.base/com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:1005) at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:848) at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) at com.tse.core_application.configuration.DataEncryptionConverter.convertToDatabaseColumn(DataEncryptionConverter.java:70) at com.tse.core_application.configuration.DataEncryptionConverter.convertToDatabaseColumn(DataEncryptionConverter.java:22)
问题分析与解决方案
核心问题
异常栈显示在convertToDatabaseColumn方法中触发了解密相关的异常,本质是JPA执行查询时,把传入的明文查询参数当成密文,调用加密方法处理——@Convert注解会同时作用于实体字段的存取值和查询参数,导致明文参数被二次加密,最终查询逻辑混乱并触发异常。
另外当前转换器存在两个关键问题:
Cipher实例线程不安全,作为成员变量复用会导致多线程状态混乱- 硬编码全0的IV,不符合CBC模式加密的安全规范,且存在安全风险
修复步骤
1. 解决查询参数转换异常
如果要基于加密字段做查询,不能依赖@Convert自动处理参数,需手动对查询参数加密后再传入仓库:
// 查询前手动加密参数 String encryptedEmail = dataEncryptionConverter.convertToDatabaseColumn(primaryEmail); User user = userRepository.findByPrimaryEmail(encryptedEmail);
若想避免手动加密,可自定义JPA查询并在语句中处理参数加密,但这种方式依赖数据库函数,通用性较差。
2. 修复线程安全问题
将Cipher改为局部变量,每次加密/解密时创建新实例,避免多线程冲突:
private Cipher getCipher(int encryptMode) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(encryptionCipher); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); cipher.init(encryptMode, getKey(), ivParameterSpec); return cipher; }
同时修改转换器的核心方法:
@SneakyThrows @Override public String convertToDatabaseColumn(Object attribute) { if (attribute == null) { return null; } Cipher cipher = getCipher(Cipher.ENCRYPT_MODE); byte[] bytes = SerializationUtils.serialize(attribute); return Base64.getEncoder().encodeToString(cipher.doFinal(bytes)); } @SneakyThrows @Override public Object convertToEntityAttribute(String dbData) { if (dbData == null) { return null; } Cipher cipher = getCipher(Cipher.DECRYPT_MODE); byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(dbData)); return SerializationUtils.deserialize(bytes); }
3. 优化加密安全性
- 生成随机IV并与密文一起存储:CBC模式要求IV随机唯一,不能固定为0。修改加密解密逻辑,将IV和密文拼接后编码:
@SneakyThrows @Override public String convertToDatabaseColumn(Object attribute) { if (attribute == null) { return null; } // 生成随机IV SecureRandom secureRandom = new SecureRandom(); byte[] iv = new byte[16]; secureRandom.nextBytes(iv); Cipher cipher = Cipher.getInstance(encryptionCipher); cipher.init(Cipher.ENCRYPT_MODE, getKey(), new IvParameterSpec(iv)); byte[] bytes = SerializationUtils.serialize(attribute); byte[] encryptedBytes = cipher.doFinal(bytes); // 拼接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 Base64.getEncoder().encodeToString(result); } @SneakyThrows @Override public Object convertToEntityAttribute(String dbData) { if (dbData == null) { return null; } byte[] data = Base64.getDecoder().decode(dbData); // 拆分IV和密文 byte[] iv = new byte[16]; byte[] encryptedBytes = new byte[data.length - iv.length]; System.arraycopy(data, 0, iv, 0, iv.length); System.arraycopy(data, iv.length, encryptedBytes, 0, encryptedBytes.length); Cipher cipher = Cipher.getInstance(encryptionCipher); cipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv)); byte[] bytes = cipher.doFinal(encryptedBytes); return SerializationUtils.deserialize(bytes); }
- 使用符合AES规范的密钥:AES要求密钥长度为16/24/32字节(对应AES-128/192/256),当前密钥
secret-test-key1仅13字节,需替换为符合长度的密钥,或通过PBKDF2等密钥派生函数生成标准长度密钥。
内容的提问来源于stack exchange,提问作者Mohan
相关产品推荐
相关产品推荐

