使用PBEWITHHMACSHA512ANDAES_256解密时遇块长度异常求助
解密时抛出"Input length must be multiple of 16"异常的解决方案
问题描述
加密字符串功能可正常运行,但解密时抛出异常:
Input length must be multiple of 16 when decrypting with padded cipher
原代码
import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Security; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; public class Trial { static String key = "USSWIF3925NJ50R"; static String userName = "sam1234t"; public static void main(String[] arg) throws IllegalBlockSizeException, BadPaddingException { Trial trial = new Trial(); Cipher cipher = trial.initialiseCipher("encrypt"); // Finally generate the transient key in bytes. byte[] transientKey = cipher.doFinal(userName.getBytes(StandardCharsets.UTF_8)); String encryptedPassword = Base64.getEncoder().encodeToString(transientKey); System.out.println("Encrypted Key: " + encryptedPassword); cipher = trial.initialiseCipher("decrypt"); // Finally decrypt the transient key in bytes. byte[] temp = cipher.doFinal(encryptedPassword.getBytes(StandardCharsets.UTF_8)); String decryptedKey = new String(Base64.getDecoder().decode(temp)); System.out.println("Decrypted Key: " + decryptedKey); } private Cipher initialiseCipher(String mode) { Cipher cipher = null; try { Security.setProperty("crypto.policy", "unlimited"); SecureRandom secureRandom = new SecureRandom(); byte[] salt = new byte[256]; byte[] ivBytes = new byte[16]; secureRandom.nextBytes(salt); secureRandom.nextBytes(ivBytes); // Generate the Key Specs. IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec); PBEKeySpec pbeKeySpec = new PBEKeySpec((key + userName).toCharArray()); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256"); SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256"); if (mode.equals("encrypt")) { cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec); } else if (mode.equals("decrypt")) { cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } return cipher; } }
运行错误输出
Encrypted Key: 9DfMJ/yuEFobrdGVsVkhpQ== Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher at com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:1005) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:848) at com.sun.crypto.provider.PBES2Core.engineDoFinal(PBES2Core.java:323) at javax.crypto.Cipher.doFinal(Cipher.java:2164) at com.att.logicalprovisioning.simulators.Trial.main(Trial.java:43)
问题根源及修复方案
两个核心问题
- 解密输入错误:直接把Base64字符串的字节数组传给
cipher.doFinal,但解密需要的是Base64解码后的原始加密字节。Base64字符串长度不一定是16的倍数,导致块大小不匹配异常。 - Salt和IV不复用:每次调用
initialiseCipher都会生成新的随机salt和IV,而PBE加密算法要求解密时必须使用和加密完全相同的salt、IV以及迭代次数,否则无法正确还原密钥和加密内容。
修复后的代码
import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Security; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; public class Trial { static String key = "USSWIF3925NJ50R"; static String userName = "sam1234t"; // 保存加密时使用的salt和IV,实际场景中可与加密数据一起存储 private static byte[] encryptionSalt; private static byte[] encryptionIv; public static void main(String[] arg) throws IllegalBlockSizeException, BadPaddingException { Trial trial = new Trial(); // 加密流程 Cipher encryptCipher = trial.initialiseCipher("encrypt"); byte[] encryptedBytes = encryptCipher.doFinal(userName.getBytes(StandardCharsets.UTF_8)); // 将salt、IV和加密数据拼接后Base64编码,方便存储/传输 byte[] combinedData = new byte[encryptionSalt.length + encryptionIv.length + encryptedBytes.length]; System.arraycopy(encryptionSalt, 0, combinedData, 0, encryptionSalt.length); System.arraycopy(encryptionIv, 0, combinedData, encryptionSalt.length, encryptionIv.length); System.arraycopy(encryptedBytes, 0, combinedData, encryptionSalt.length + encryptionIv.length, encryptedBytes.length); String encryptedString = Base64.getEncoder().encodeToString(combinedData); System.out.println("Encrypted String: " + encryptedString); // 解密流程 byte[] decodedCombined = Base64.getDecoder().decode(encryptedString); // 拆分出salt、IV和加密数据 byte[] decryptionSalt = new byte[256]; byte[] decryptionIv = new byte[16]; byte[] encryptedData = new byte[decodedCombined.length - 256 - 16]; System.arraycopy(decodedCombined, 0, decryptionSalt, 0, 256); System.arraycopy(decodedCombined, 256, decryptionIv, 0, 16); System.arraycopy(decodedCombined, 256 + 16, encryptedData, 0, encryptedData.length); Cipher decryptCipher = trial.initialiseCipher("decrypt", decryptionSalt, decryptionIv); byte[] decryptedBytes = decryptCipher.doFinal(encryptedData); String decryptedKey = new String(decryptedBytes, StandardCharsets.UTF_8); System.out.println("Decrypted Key: " + decryptedKey); } // 重载方法:加密时自动生成salt和IV private Cipher initialiseCipher(String mode) { return initialiseCipher(mode, null, null); } // 重载方法:解密时传入加密时使用的salt和IV private Cipher initialiseCipher(String mode, byte[] customSalt, byte[] customIv) { Cipher cipher = null; try { Security.setProperty("crypto.policy", "unlimited"); SecureRandom secureRandom = new SecureRandom(); byte[] salt; byte[] ivBytes; // 加密模式生成新的salt和IV并保存;解密模式使用传入的参数 if (mode.equals("encrypt")) { salt = new byte[256]; ivBytes = new byte[16]; secureRandom.nextBytes(salt); secureRandom.nextBytes(ivBytes); encryptionSalt = salt; encryptionIv = ivBytes; } else { salt = customSalt; ivBytes = customIv; } IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec); PBEKeySpec pbeKeySpec = new PBEKeySpec((key + userName).toCharArray()); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256"); SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256"); if (mode.equals("encrypt")) { cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec); } else if (mode.equals("decrypt")) { cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec); } } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { e.printStackTrace(); } return cipher; } }
关键修改说明
- 解密输入修正:解密时先对Base64字符串解码,拆分出salt、IV和原始加密数据,再将加密数据传给
cipher.doFinal。 - Salt和IV复用:加密时生成并保存salt和IV,解密时传入相同值;实际项目中可将三者拼接后一起存储/传输,避免参数丢失。
- 方法重载:新增带salt和IV参数的初始化方法,适配解密场景的参数需求。
内容的提问来源于stack exchange,提问作者hell_storm2004
相关产品推荐
相关产品推荐

