AES加密输出长度不固定致数据库字段截断问题求助
解决AES/CBC加密密文长度不一致导致数据库字段截断问题
问题根源
你遇到的Data truncation: Data too long for column异常本质上有两个核心原因:
- AES块加密的特性:AES是块大小为16字节的块加密算法,PKCS5Padding会自动将明文填充到16字节的整数倍。加密后的二进制密文长度是
ceil(明文字节数 / 16) * 16,转成十六进制(Hex)字符串后,每个字节对应2个字符,因此密文Hex长度是密文字节数的2倍。比如:- 短明文(如"test",4字节)会被填充到16字节,Hex密文长度为32字符
- 较长明文(如"this is test",12字节)同样填充到16字节,Hex密文长度也是32字符(你提供的示例密文可能是笔误,正常情况下同块数的明文加密后Hex长度一致)
- 如果明文超过16字节(比如17字节),会被填充到32字节,Hex密文长度变为64字符
- 数据库字段长度不足:你的
firstName字段定义为varchar(25),但哪怕最短的Hex密文都需要32字符,远远超过字段限制,必然触发截断异常。
修复步骤
1. 调整数据库字段长度(或类型)
根据firstName的最大明文长度计算所需的最大密文长度:
- 假设
firstName最多25个UTF-8字符,每个字符最多占3字节,总字节数为25*3=75字节 - 填充后密文字节数为
ceil(75/16)*16=80字节 - 转Hex后字符串长度为
80*2=160字符
因此,建议将字段修改为:
ALTER TABLE user MODIFY COLUMN firstName VARCHAR(160);
或者更高效的方式,直接存储二进制密文(节省空间):
ALTER TABLE user MODIFY COLUMN firstName VARBINARY(80);
(如果用二进制存储,加密时直接存cipherBytes,解密时读取字节数组即可,无需Hex转换)
2. 修复加密代码中的安全隐患(重要!)
你的代码中IV(初始化向量)使用了全0的字节数组,这在CBC模式下是严重的安全问题——相同的明文会生成相同的密文,容易被破解。必须生成随机唯一的IV,并将IV与密文一起存储:
修改后的加密方法
public String encrypt(String plainText) { byte[] cipherBytes = null; log.info("Started encryption..."); if (plainText != null && !plainText.isEmpty()) { if (cipher != null && key != null) { try { // 生成随机IV(AES块大小为16字节) byte[] ivByte = new byte[cipher.getBlockSize()]; new SecureRandom().nextBytes(ivByte); IvParameterSpec ivParamsSpec = new IvParameterSpec(ivByte); cipher.init(Cipher.ENCRYPT_MODE, key, ivParamsSpec); cipherBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); // 将IV和密文拼接(IV在前,密文在后),再转Hex byte[] combined = ByteBuffer.allocate(ivByte.length + cipherBytes.length) .put(ivByte) .put(cipherBytes) .array(); plainText = Hex.encodeHexString(combined); log.info("Completed encryption."); log.info("Encrypted data (Hex): " + plainText); } catch (BadPaddingException | IllegalBlockSizeException | InvalidKeyException | InvalidAlgorithmParameterException e) { log.error("Encryption failed : " + e.getMessage()); e.printStackTrace(); throw new RuntimeException("Encryption failed : " + e.getMessage()); } } else { log.error("Encryption failed, cipher, key is null."); throw new RuntimeException("Encryption failed, cipher, key is null."); } } return plainText; }
修改后的解密方法
public String decrypt(String cipherHexString) throws AuthorizationException { log.info("Started decryption..."); String resource = "kk"; String resourceCatagory = "kk tables"; String accessType = "write"; try { if (decryptionAuthorizer.authorize(resource, resourceCatagory, accessType)) { if (cipherHexString != null && !cipherHexString.isEmpty()) { if (cipher != null && key != null) { try { byte[] combined = Hex.decodeHex(cipherHexString.toCharArray()); // 分离IV和密文(前16字节是IV) byte[] ivByte = Arrays.copyOfRange(combined, 0, cipher.getBlockSize()); byte[] cipherBytes = Arrays.copyOfRange(combined, cipher.getBlockSize(), combined.length); IvParameterSpec ivParamsSpec = new IvParameterSpec(ivByte); cipher.init(Cipher.DECRYPT_MODE, key, ivParamsSpec); byte[] plainTextBytes = cipher.doFinal(cipherBytes); String plainText = new String(plainTextBytes, StandardCharsets.UTF_8); log.info("Completed decryption."); return plainText; } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException | DecoderException e) { log.error("Decryption failed : " + e.getMessage()); e.printStackTrace(); throw new RuntimeException("Decryption failed : " + e.getMessage()); } } else { log.error("Decryption failed, cipher, key is null."); throw new RuntimeException("Decryption failed, cipher, key is null."); } } } } catch (AuthorizationException e) { throw new AuthorizationException("User not authorized to decrypt data."); } return cipherHexString; }
3. 其他优化建议
- 统一使用
StandardCharsets.UTF_8代替硬编码的"UTF8",避免编码异常 - 移除加密方法中
new String(cipherBytes, "UTF8")的日志输出,因为二进制密文转UTF-8字符串会产生乱码,没有实际意义
内容的提问来源于stack exchange,提问作者Niranga Sandaruwan
相关产品推荐
相关产品推荐

