求助:如何在Java/Scala中实现AES 256 CBC解密(已支持OpenSSL解密)
Java/Scala实现兼容OpenSSL的AES-256解密
既然你已经能用OpenSSL搞定解密了,那把这个逻辑迁移到Java或Scala里其实就是要对齐OpenSSL的加密格式和密钥派生规则,我来给你一步步拆解并提供可直接运行的代码。
首先明确几个核心前提:
- 你提供的载荷是OpenSSL标准AES加密输出,格式为
Salted__前缀 + 8字节盐值 + 实际密文,整体做了Base64编码 - OpenSSL默认用
EVP_BytesToKey算法生成加密密钥和IV,参数为:哈希算法MD5、迭代次数1、盐值从载荷中提取
Java 实现代码
无需额外依赖,JDK8及以上即可直接运行:
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; public class OpenSSLAesDecryptor { private static final String OPENSSL_SALT_PREFIX = "Salted__"; private static final int SALT_LENGTH = 8; private static final int AES_KEY_LENGTH = 256; private static final int IV_LENGTH = 16; private static final String HASH_ALGORITHM = "MD5"; private static final int ITERATION_COUNT = 1; public static String decrypt(String base64Payload, String base64Key) throws Exception { // 1. 解码Base64格式的载荷和密钥 byte[] payloadBytes = Base64.getDecoder().decode(base64Payload); byte[] keyBytes = Base64.getDecoder().decode(base64Key); // 2. 验证前缀并提取盐值 if (!new String(payloadBytes, 0, OPENSSL_SALT_PREFIX.length(), StandardCharsets.UTF_8).equals(OPENSSL_SALT_PREFIX)) { throw new IllegalArgumentException("无效的OpenSSL加密载荷:缺少Salted__前缀"); } byte[] salt = new byte[SALT_LENGTH]; System.arraycopy(payloadBytes, OPENSSL_SALT_PREFIX.length(), salt, 0, SALT_LENGTH); // 3. 提取实际密文(跳过前缀和盐值) byte[] ciphertext = new byte[payloadBytes.length - OPENSSL_SALT_PREFIX.length() - SALT_LENGTH]; System.arraycopy(payloadBytes, OPENSSL_SALT_PREFIX.length() + SALT_LENGTH, ciphertext, 0, ciphertext.length); // 4. 模拟OpenSSL的EVP_BytesToKey生成密钥和IV byte[] keyAndIv = generateKeyAndIv(keyBytes, salt); byte[] aesKey = new byte[AES_KEY_LENGTH / 8]; byte[] iv = new byte[IV_LENGTH]; System.arraycopy(keyAndIv, 0, aesKey, 0, aesKey.length); System.arraycopy(keyAndIv, aesKey.length, iv, 0, iv.length); // 5. 初始化AES-CBC解密器 SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // 6. 解密并返回明文 byte[] plaintextBytes = cipher.doFinal(ciphertext); return new String(plaintextBytes, StandardCharsets.UTF_8); } private static byte[] generateKeyAndIv(byte[] passwordBytes, byte[] salt) throws Exception { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmac" + HASH_ALGORITHM); PBEKeySpec spec = new PBEKeySpec( new String(passwordBytes, StandardCharsets.UTF_8).toCharArray(), salt, ITERATION_COUNT, AES_KEY_LENGTH + IV_LENGTH ); SecretKey secretKey = factory.generateSecret(spec); return secretKey.getEncoded(); } public static void main(String[] args) throws Exception { // 替换成你的密钥和载荷 String targetKey = "5UX8IBWruBk1QmMZlZ1ESYmZRiC9w1DsrPpLIP9QF+Q="; String targetPayload = "U2FsdGVkX19XO99r3f7LgNbPTW8tKexv26mCPUYTMTTiwSSayFvB/QraYJkfzKjEB+tisqzzrU9aZu/tQ5CIVrHHwkUxpyjKEjx3N5q+Ba3weNK/NthpcCsNw5GQxl3NWGoDPe2IFXHMpvpy9xb2mbMnPtwr3m4nF3JzRD6Ft34Q7bHmmTCDkh5kEF9Hx+nbeiLURqLJ1S5YeGq7xhZqalimuQPwT7cr3MPkWPGyZVtNtrKJfIRStoMqP9F2qvm6"; String result = decrypt(targetPayload, targetKey); System.out.println("解密结果:"); System.out.println(result); } }
Scala 实现代码
Scala可以直接复用Java加密API,代码更简洁:
import java.nio.charset.StandardCharsets import java.util.Base64 import javax.crypto.{Cipher, SecretKeyFactory} import javax.crypto.spec.{IvParameterSpec, PBEKeySpec, SecretKeySpec} object OpenSSLAesDecryptor { private val OPENSSL_SALT_PREFIX = "Salted__" private val SALT_LENGTH = 8 private val AES_KEY_LENGTH = 256 private val IV_LENGTH = 16 private val HASH_ALGORITHM = "MD5" private val ITERATION_COUNT = 1 def decrypt(base64Payload: String, base64Key: String): String = { // 解码Base64 val payloadBytes = Base64.getDecoder.decode(base64Payload) val keyBytes = Base64.getDecoder.decode(base64Key) // 验证前缀并提取盐值 require( new String(payloadBytes.take(OPENSSL_SALT_PREFIX.length), StandardCharsets.UTF_8) == OPENSSL_SALT_PREFIX, "无效的OpenSSL加密载荷:缺少Salted__前缀" ) val salt = payloadBytes.slice(OPENSSL_SALT_PREFIX.length, OPENSSL_SALT_PREFIX.length + SALT_LENGTH) val ciphertext = payloadBytes.drop(OPENSSL_SALT_PREFIX.length + SALT_LENGTH) // 生成密钥和IV val keyAndIv = generateKeyAndIv(keyBytes, salt) val aesKey = keyAndIv.take(AES_KEY_LENGTH / 8) val iv = keyAndIv.slice(aesKey.length, aesKey.length + IV_LENGTH) // 初始化解密器 val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec(aesKey, "AES"), new IvParameterSpec(iv) ) // 解密并返回明文 new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8) } private def generateKeyAndIv(passwordBytes: Array[Byte], salt: Array[Byte]): Array[Byte] = { val factory = SecretKeyFactory.getInstance(s"PBKDF2WithHmac$HASH_ALGORITHM") val spec = new PBEKeySpec( new String(passwordBytes, StandardCharsets.UTF_8).toCharArray, salt, ITERATION_COUNT, AES_KEY_LENGTH + IV_LENGTH ) factory.generateSecret(spec).getEncoded } def main(args: Array[String]): Unit = { // 替换成你的密钥和载荷 val targetKey = "5UX8IBWruBk1QmMZlZ1ESYmZRiC9w1DsrPpLIP9QF+Q=" val targetPayload = "U2FsdGVkX19XO99r3f7LgNbPTW8tKexv26mCPUYTMTTiwSSayFvB/QraYJkfzKjEB+tisqzzrU9aZu/tQ5CIVrHHwkUxpyjKEjx3N5q+Ba3weNK/NthpcCsNw5GQxl3NWGoDPe2IFXHMpvpy9xb2mbMnPtwr3m4nF3JzRD6Ft34Q7bHmmTCDkh5kEF9Hx+nbeiLURqLJ1S5YeGq7xhZqalimuQPwT7cr3MPkWPGyZVtNtrKJfIRStoMqP9F2qvm6" val result = decrypt(targetPayload, targetKey) println("解密结果:") println(result) } }
关键注意事项
- OpenSSL格式适配:必须先提取
Salted__前缀后的8字节盐值,这是OpenSSL加密时自动添加的标识 - 密钥派生逻辑:OpenSSL默认用MD5哈希1次生成密钥和IV,我们用
PBKDF2WithHmacMD5模拟该逻辑(迭代1次时结果完全一致) - 加密配置:代码使用AES-256-CBC模式+PKCS5填充,和OpenSSL默认配置完全对齐
- 密钥处理:你提供的Base64密钥解码后正好是32字节(AES-256要求的密钥长度),可直接用于派生逻辑
直接运行上述代码即可得到和OpenSSL解密一致的结果。
内容的提问来源于stack exchange,提问作者Rumesh Krishnan
相关产品推荐
相关产品推荐

