我正在尝试将此nodejs代码移植到java
const crypto = require("crypto");
const encrypt = (data, key) => {
const cipher = crypto.createCipher('aes192', key)
let crypted = cipher.update(data, 'utf8', 'hex')
crypted += cipher.final('hex')
return crypted;
}
我尝试过使用这个解决方案:
import org.springframework.security.crypto.codec.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public String encrypt(String data, String key) {
try {
var cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(), "AES"));
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;
}
缺点:
Java的encrypt
方法返回的值与nodejs的方法不同(对于相同的数据和键(。
在nodejs中,我可以放一个短键(5个字符长(,同时在java中我会捕获一个异常,例如";java.security.InvalidKeyException:无效的AES密钥长度:5字节">
你能提出正确的解决方案或指出现有解决方案中的错误吗?提前谢谢!
注意:我无法在nodejs中更改加密/解密方法,所以我需要将其正确地移植到java。
encrypt
java版本与javascript版本不使用相同的逻辑。
encrypt
javascript函数接受密码(参数名称key
具有误导性(,然后将其传递给createCipher
。createCipher
不直接使用密码,而是从中派生出一个密钥。它是用于加密消息的派生密钥。来自NodeJ的文档:
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密钥具有固定大小。它们只能是128、192或256位长。(以字节8、16、32为单位(。如果使用不同大小的密钥,则会得到异常InvalidKeyException
。NodeJS没有抱怨一个";无效密钥长度";因为实际上你使用的是密码,而不是钥匙。NodeJS在加密数据之前从密码中派生一个密钥。
(如文档中所述(NodeJs使用OpenSSL对数据进行加密,并使用OpenSSL特有的函数导出密钥:EVP_BytesToKey
。
幸运的是,这个SO答案在Java中实现了EVP_BytesToKey
。(代码来自此博客条目(
我修改了你的代码以使用它。我在答案的末尾添加了最终结果。我很少写安全代码,在这种情况下,我只是调整了现有的解决方案,所以如果你决定使用它,你应该审查代码(或者让其他人审查,如果你的公司有安全团队的话(。
最后一条评论:createCipher
已弃用。但是您在问题中说,您不能更改javascriptencrypt
版本的实现。(如果您不是encrypt
的维护者(您应该与维护者讨论弃用问题,以了解他们仍然使用createCipher
(使用EVP_BytesToKey(的原因。EVP_BytesToKey
被认为是一个弱密钥派生函数,OpenSSL建议在较新的应用程序中使用更现代的函数。
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;
}
}