如何将加密(AES192)方法从nodejs正确移植到java?
How to port encrypt (AES192) method from nodejs to java correctly?
我正在尝试将此 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;
}
缺点:
encrypt
方法 Java returns 与 nodejs 方法不同的值(对于相同的数据和键)。
在 nodejs 中,我可以放置一个短键(5 个字符长),同时在 java 我正在捕获异常,例如“java.security.InvalidKeyException:无效的 AES 密钥长度:5 个字节”
您能否提出正确的解决方案或指出现有解决方案中的错误?提前致谢!
注意:我无法在 nodejs 中更改 encrypt/decrypt 方法,因此我需要将其正确移植到 java。
您的 encrypt
java 版本使用的逻辑与 java 脚本版本不同。
encrypt
java脚本函数接受一个密码(参数名称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 has an implementation of EVP_BytesToKey
in Java. (The code is originally from this blog entry)
我修改了您的代码以使用它。我将最终结果添加到答案的末尾。我很少写安全代码,在这种情况下我只是改编了一个现有的解决方案,所以如果你决定使用它,你应该审查代码(或者如果你的公司有安全团队,请让其他人审查它)。
最后一条评论:createCipher
已弃用。但是你在你的问题中说你不能改变 javascript encrypt
版本的实现。 (如果您不是 encrypt
的维护者)您应该与维护者讨论弃用问题,以了解他们仍然使用 createCipher
(使用 EVP_BytesToKey)的原因。 EVP_BytesToKey
被认为是 and 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;
}
}
我正在尝试将此 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;
}
缺点:
encrypt
方法 Java returns 与 nodejs 方法不同的值(对于相同的数据和键)。在 nodejs 中,我可以放置一个短键(5 个字符长),同时在 java 我正在捕获异常,例如“java.security.InvalidKeyException:无效的 AES 密钥长度:5 个字节”
您能否提出正确的解决方案或指出现有解决方案中的错误?提前致谢!
注意:我无法在 nodejs 中更改 encrypt/decrypt 方法,因此我需要将其正确移植到 java。
您的 encrypt
java 版本使用的逻辑与 java 脚本版本不同。
encrypt
java脚本函数接受一个密码(参数名称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 has an implementation of EVP_BytesToKey
in Java. (The code is originally from this blog entry)
我修改了您的代码以使用它。我将最终结果添加到答案的末尾。我很少写安全代码,在这种情况下我只是改编了一个现有的解决方案,所以如果你决定使用它,你应该审查代码(或者如果你的公司有安全团队,请让其他人审查它)。
最后一条评论:createCipher
已弃用。但是你在你的问题中说你不能改变 javascript encrypt
版本的实现。 (如果您不是 encrypt
的维护者)您应该与维护者讨论弃用问题,以了解他们仍然使用 createCipher
(使用 EVP_BytesToKey)的原因。 EVP_BytesToKey
被认为是
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;
}
}