
CryptoJS 的 DES 加密默认使用 OpenSSL 兼容的 EVP_BytesToKey 密钥派生(含随机 salt)、CBC 模式和 PKCS#5/7 填充,并在密文前拼接 "Salted__" 头及 8 字节 salt;Java 需用 BouncyCastle 实现相同派生逻辑才能互通。
cryptojs 的 des 加密默认使用 openssl 兼容的 `evp_bytestokey` 密钥派生(含随机 salt)、cbc 模式和 pkcs#5/7 填充,并在密文前拼接 `"salted__"` 头及 8 字节 salt;java 需用 bouncycastle 实现相同派生逻辑才能互通。
CryptoJS 的 CryptoJS.DES.encrypt(plain, password) 并非直接使用原始密码作为 DES 密钥,而是通过 OpenSSL 风格的密钥派生函数 EVP_BytesToKey(基于 MD5 多次迭代)从密码字符串和随机 salt 中派生出 8 字节 DES 密钥及 8 字节 IV。同时,加密采用 DES/CBC/PKCS5Padding 模式,最终输出为 Base64 编码的 "Salted__" + salt + ciphertext 结构(共 16 字节头部 + salt + 密文)。这正是导致你 Java 原始 ECB 实现结果不一致的根本原因:缺少 salt、错误的模式(ECB vs CBC)、缺失密钥派生。
以下为完整、可运行的 Java 互通实现(需引入 BouncyCastle):
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
✅ 依赖配置(Maven)
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>✅ Java 加密实现(兼容 CryptoJS)
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class CryptoJsDesCompat {
private static final String SALTED_PREFIX = "Salted__";
private static final int SALT_LEN = 8;
public static String encrypt(String plaintext, String password) throws Exception {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_LEN];
random.nextBytes(salt);
// 使用 EVP_BytesToKey (MD5, 1 iteration) 派生 key & iv
OpenSSLPBEParametersGenerator gen = new OpenSSLPBEParametersGenerator(new MD5Digest());
gen.init(password.getBytes(StandardCharsets.UTF_8), salt);
ParametersWithIV params = (ParametersWithIV) gen.generateDerivedParameters(64, 64); // 64-bit = 8 bytes
byte[] key = ((KeyParameter) params.getParameters()).getKey();
byte[] iv = params.getIV();
// DES/CBC/PKCS5Padding 加密
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"), new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 拼接 Salted__ + salt + ciphertext
ByteBuffer buf = ByteBuffer.allocate(SALTED_PREFIX.length() + SALT_LEN + ciphertext.length);
buf.put(SALTED_PREFIX.getBytes(StandardCharsets.UTF_8))
.put(salt)
.put(ciphertext);
return Base64.getEncoder().encodeToString(buf.array());
}
public static String decrypt(String base64Ciphertext, String password) throws Exception {
byte[] input = Base64.getDecoder().decode(base64Ciphertext);
// 校验 Salted__ 头部
if (input.length < SALTED_PREFIX.length() + SALT_LEN) {
throw new IllegalArgumentException("Invalid ciphertext: missing Salted__ header or salt");
}
if (!new String(input, 0, SALTED_PREFIX.length(), StandardCharsets.UTF_8).equals(SALTED_PREFIX)) {
throw new IllegalArgumentException("Invalid ciphertext: missing 'Salted__' prefix");
}
byte[] salt = new byte[SALT_LEN];
System.arraycopy(input, SALTED_PREFIX.length(), salt, 0, SALT_LEN);
byte[] ciphertext = new byte[input.length - SALTED_PREFIX.length() - SALT_LEN];
System.arraycopy(input, SALTED_PREFIX.length() + SALT_LEN, ciphertext, 0, ciphertext.length);
// 派生 key & iv(同加密逻辑)
OpenSSLPBEParametersGenerator gen = new OpenSSLPBEParametersGenerator(new MD5Digest());
gen.init(password.getBytes(StandardCharsets.UTF_8), salt);
ParametersWithIV params = (ParametersWithIV) gen.generateDerivedParameters(64, 64);
byte[] key = ((KeyParameter) params.getParameters()).getKey();
byte[] iv = params.getIV();
// 解密
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "DES"), new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted, StandardCharsets.UTF_8);
}
}✅ 使用示例
public class Test {
public static void main(String[] args) throws Exception {
String plain = "123456";
String password = "12345678";
String encrypted = CryptoJsDesCompat.encrypt(plain, password);
System.out.println("Encrypted (Base64): " + encrypted);
// 输出类似:U2FsdGVkX19Z3jzZ3NvmJey7mrbU2qhT
String decrypted = CryptoJsDesCompat.decrypt(encrypted, password);
System.out.println("Decrypted: " + decrypted); // 输出:123456
}
}⚠️ 关键注意事项
-
必须使用 BouncyCastle:JDK 原生不提供
EVP_BytesToKey实现,OpenSSLPBEParametersGenerator是关键。 - salt 必须随机且参与派生:每次加密 salt 不同 → 密文不同,但解密时能正确还原。
-
模式严格匹配:CryptoJS DES 默认为
CBC+PKCS#5(等价于 PKCS#7),不可用ECB或NoPadding。 -
字符编码统一:全程使用
StandardCharsets.UTF_8,避免平台差异。 -
安全性警示:DES 密钥仅 56 位有效,已严重过时;
EVP_BytesToKey迭代次数固定(1 次 MD5),抗暴力能力极弱。生产环境请务必升级至 AES-256 + PBKDF2WithHmacSHA256(10万+ 迭代)。
通过以上实现,Java 与 CryptoJS 的 DES 加解密即可完全互通,输出一致、可互操作。

















