Java解密从OpenSSL(>1.1.1 -md sha256)迁移到(<1.1.1 --pbkdf2)失败求助
Java适配OpenSSL 1.1.1+ PBKDF2加解密报错问题解决
问题背景
原Java代码适配OpenSSL 1.1.1以下版本的命令:
echo -n "password" | openssl enc -aes-256-cbc -a -k secretKey -md sha256
升级到OpenSSL 1.1.1+后改用-pbkdf2参数:
echo -n "password" | openssl enc -aes-256-cbc -a -k secretKey -pbkdf2
修改Java代码使用PBKDF2withHmacSHA256生成密钥后,解密时抛出错误:
Exception in thread "main" modified.EncryptionDecryptionException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
核心错误原因
- 密钥推导的密码输入错误:
getNewKeyAndIvThatSupportPBKDF方法错误传入了密文的char数组作为PBKDF2的密码参数,而OpenSSL的-k secretKey是用secretKey作为密码推导密钥,与密文无关。 - 算法名称大小写问题:Java中PBKDF2标准算法名称为
PBKDF2WithHmacSHA256(首字母大写),原代码中的PBKDF2withHmacSHA256可能导致部分JDK环境无法识别。 - 参数传递逻辑混乱:
getCipher方法保留了旧逻辑的冗余变量,干扰了新密钥推导流程的正确性。
修复步骤
- 修正PBKDF2密码输入:调用密钥推导方法时,传入
SECRET_KEY的char数组(即OpenSSL命令中的-k参数值),而非密文内容。 - 统一算法名称:使用标准大写的
PBKDF2WithHmacSHA256算法名称。 - 清理冗余代码:移除未使用的
passAndSalt等变量,简化密钥推导流程。 - 对齐OpenSSL参数:确保PBKDF2迭代次数与OpenSSL默认值(10000)一致。
修正后的完整代码
import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Decoder; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public class AESWithPBKDF2 { private static final String SECRET_KEY = "secretKey"; private static final String SALT_KEY = "Salted__"; private static final int KEY_LEN = 32; private static final int IV_LEN = 16; // OpenSSL -pbkdf2 默认迭代次数为10000 private static final int PBKDF2_ITERATIONS = 10000; public static void main(String[] args) throws Exception { // 测试OpenSSL命令生成的密文 String example = "U2FsdGVkX1+Z9ZkKZ8e6eQ=="; // 替换为实际生成的密文 String decryptedText = decryptPassword(example.toCharArray()); System.out.println("解密结果: " + decryptedText); // 测试加密后解密的一致性 String plainText = "password"; String encrypted = encryptPassword(plainText.toCharArray()); System.out.println("加密结果: " + encrypted); String decrypted = decryptPassword(encrypted.toCharArray()); System.out.println("解密验证: " + decrypted); } private AESWithPBKDF2() { throw new IllegalStateException("Utility class"); } public static String decryptPassword(char[] source) throws EncryptionDecryptionException { return decrypt(source, SECRET_KEY); } public static String encryptPassword(char[] source) throws EncryptionDecryptionException { return encrypt(source, SECRET_KEY); } private static String decrypt(char[] source, String sk) throws EncryptionDecryptionException { try { final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8); final Decoder decoder = Base64.getDecoder(); final byte[] inBytes = decoder.decode(String.valueOf(source)); checkMagic(magic, inBytes); byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8); final Cipher cipher = getCipher(Cipher.DECRYPT_MODE, sk.toCharArray(), salt); final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16); return new String(clear, StandardCharsets.UTF_8); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new EncryptionDecryptionException(e.getMessage(), e); } } private static String encrypt(char[] source, String sk) throws EncryptionDecryptionException { try { final byte[] magic = SALT_KEY.getBytes(StandardCharsets.UTF_8); final byte[] inBytes = String.valueOf(source).getBytes(StandardCharsets.UTF_8); byte[] salt = new SecureRandom().generateSeed(8); final Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, sk.toCharArray(), salt); byte[] data = cipher.doFinal(inBytes); data = concat(concat(magic, salt), data); return Base64.getEncoder().encodeToString(data); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new EncryptionDecryptionException(e.getMessage(), e); } } private static Cipher getCipher(int cipherMode, char[] password, byte[] salt) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { byte[] keyAndIv = getPBKDF2KeyAndIv(password, salt); final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, KEY_LEN); final byte[] iv = Arrays.copyOfRange(keyAndIv, KEY_LEN, KEY_LEN + IV_LEN); final SecretKeySpec key = new SecretKeySpec(keyValue, "AES"); final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(cipherMode, key, new IvParameterSpec(iv)); return cipher; } private static byte[] concat(final byte[] a, final byte[] b) { final byte[] c = new byte[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } private static void checkMagic(final byte[] magic, final byte[] inBytes) { final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, magic.length); if (!Arrays.equals(shouldBeMagic, magic)) { throw new EncryptionDecryptionException("解密异常: 无效的魔术头"); } } private static byte[] getPBKDF2KeyAndIv(char[] password, byte[] salt) throws EncryptionDecryptionException { try { PBEKeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, (KEY_LEN + IV_LEN) * 8); return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") .generateSecret(spec) .getEncoded(); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { throw new EncryptionDecryptionException(e.getMessage(), e); } } // 自定义异常类 public static class EncryptionDecryptionException extends Exception { public EncryptionDecryptionException(String message, Throwable cause) { super(message, cause); } } }
内容的提问来源于stack exchange,提问作者Illidan
相关产品推荐
相关产品推荐

