您的encrypt java 版本使用的逻辑与 javascript 版本不同。
encrypt javascript 函数接受一个密码(参数名称key 具有误导性)然后将其传递给createCipher。 createCipher 不直接使用密码,而是从中派生出一个密钥。它是用于加密消息的派生密钥。 From NodeJs documentation:
The password is used to derive the cipher key and initialization vector (IV). The value must be either a 'latin1' encoded string, a Buffer, a TypedArray, or a DataView.
另一方面,encrypt java 函数需要一个随时可用的密钥,因此您必须自己从密码中导出密钥。
AES 密钥具有固定大小。 They can only be 128, 192 or 256 bits long。 (以字节 8、16、32 字节长)。如果您使用不同大小的密钥,您将收到异常InvalidKeyException。 NodeJS 没有抱怨“无效的密钥长度”,因为实际上您使用的是密码,而不是密钥。 NodeJS 在加密数据之前从密码中派生出一个密钥。
(如文档中所述)NodeJs 使用 OpenSSL 加密数据并使用特定于 OpenSSL 的函数派生密钥:EVP_BytesToKey。
幸运的是,SO answer 在 Java 中实现了 EVP_BytesToKey。 (代码原来自this blog entry)
我修改了您的代码以使用它。我将最终结果添加到答案的末尾。我很少编写安全代码,在这种情况下,我只是调整了一个现有的解决方案,因此如果您决定使用该代码(或者如果您的公司有安全团队,请让其他人进行审查)。
最后一条评论:createCipher 已弃用。但是您在问题中说您无法更改 javascript encrypt 版本的实现。 (如果您不是encrypt 的维护者)您应该与维护者讨论弃用问题,以了解他们仍然使用createCipher(使用EVP_BytesToKey)的原因。 EVP_BytesToKey 被认为是 weak key derivation function 和 OpenSSL recommends using more modern functions for newer applications。
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.security.crypto.codec.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Main {
public static void main(String[] args){
System.out.println("Result : " + encrypt("my secret message","pass"));
}
public static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data, int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if(data == null) {
return both;
}
int addmd = 0;
for(;;) {
md.reset();
if(addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if(null != salt) {
md.update(salt,0,8);
}
md_buf = md.digest();
for(i=1;i<count;i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i=0;
if(nkey > 0) {
for(;;) {
if(nkey == 0) break;
if(i == md_buf.length) break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if(niv > 0 && i != md_buf.length) {
for(;;) {
if(niv == 0) break;
if(i == md_buf.length) break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if(nkey == 0 && niv == 0) {
break;
}
}
for(i=0;i<md_buf.length;i++) {
md_buf[i] = 0;
}
return both;
}
public static String encrypt(String data, String password) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
var keySizeBits = 192 / Byte.SIZE; //AES with 192 bits key = 16 bytes
var ivSize = cipher.getBlockSize();
final byte[][] keyAndIV = EVP_BytesToKey(keySizeBits, ivSize, md5, null, password.getBytes(StandardCharsets.US_ASCII), 1);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
var cipherText = cipher.update(data.getBytes());
cipherText = ArrayUtils.addAll(cipherText, cipher.doFinal());
return new String(Hex.encode(cipherText));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}