Java 的 AES/GCM/NoPadding 相当于 JS
Java's AES/GCM/NoPadding equivalent in JS
我们在我们的组织中使用第三方的服务,我们必须以加密方式向他们发送一些数据。最近,他们将加密算法更新为 AES/GCM/NoPadding.
他们在 java 中有他们的代码,而我们使用 java 脚本。他们在 Java 中与我们分享了算法的实现,我们必须在 JS 中复制和实现该算法,因为这就是我们使用的。
我在转换此代码时遇到了挑战。附上两个 Java 实现,它像一个魅力一样工作,并且 JS 代码没有按预期工作。虽然我尝试了很多东西,但其中 none 对我有用。所以,我只分享我尝试过的最新代码。
我不了解 Java 或密码学,因此非常感谢这方面的任何帮助。
JAVA代码-
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption class for managing all types of AES encryptions
*/
public class EncryptionUtil {
private final Builder mBuilder;
private final static String HEX = "0123456789ABCDEF";
private EncryptionUtil(Builder builder) {
mBuilder = builder;
}
public static EncryptionUtil getDefault(String key, String salt, byte[] iv) {
try {
return Builder.getDefaultBuilder(key, salt, iv).build();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
public String encryptOrNull(String data) {
try {
return encrypt(data);
} catch (Exception e) {
return "";
}
}
private String encrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doEncryptAES(data, secretKey, mBuilder.getAlgorithm(), mBuilder.getCharsetName());
}
private String decrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doDecryptAES(data, secretKey, mBuilder.getAlgorithm());
}
private String doEncryptAES(String inputString,
SecretKey key, String xForm, String charset) throws Exception {
byte inpBytes[] = inputString.getBytes(charset);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, mBuilder.getIv()));
byte[] encryptedData = cipher.doFinal(inpBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + mBuilder.getIv().length + encryptedData.length);
byteBuffer.putInt(mBuilder.getIv().length);
byteBuffer.put(mBuilder.getIv());
byteBuffer.put(encryptedData);
return toHex(byteBuffer.array());
}
return toHex(cipher.doFinal(inpBytes));
}
/**
* for AES in GCM mode kitkat version is required
*
* @param inputString is String we want to decrypt
* @param key is symmetric key use for decryption and it similar to key used for encryption (128,192,256)
* @param xForm is the transformation form in which form we want to transform
* (AES/ECB/PKCS5Padding,AES/ECB/NoPadding,AES/CBC/PKCS5Padding,AES/CBC/NoPadding,AES/GCM/NoPadding)
* @return it reurn decrypted string
* @throws Exception NOSuchAlgorithmEXception,NoSuchPaddingEXception
*/
private String doDecryptAES(String inputString,
SecretKey key, String xForm) throws Exception {
byte[] inpBytes = toByte(inputString);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
ByteBuffer byteBuffer = ByteBuffer.wrap(inpBytes);
int noonceSize = byteBuffer.getInt();
if (noonceSize < 12 || noonceSize >= 16)
throw new IllegalArgumentException("Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file.");
byte[] iv = new byte[noonceSize];
byteBuffer.get(iv);
byte[] cipherBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
return new String(cipher.doFinal(cipherBytes), mBuilder.getCharsetName());
}
return new String(cipher.doFinal(inpBytes), mBuilder.getCharsetName());
}
public String decryptOrNull(String data) {
try {
return decrypt(data);
} catch (Exception e) {
return "";
}
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return toHex(messageDigest.digest()).toCharArray();
}
private byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
return result;
}
public String toHex(byte[] stringBytes) {
StringBuffer result = new StringBuffer(2 * stringBytes.length);
for (int i = 0; i < stringBytes.length; i++) {
result.append(HEX.charAt((stringBytes[i] >> 4) & 0x0f)).append(HEX.charAt(stringBytes[i] & 0x0f));
}
return result.toString();
}
private static class Builder {
private byte[] mIv;
private int mKeyLength;
private int mIterationCount;
private String mSalt;
private String mKey;
private String mAlgorithm;
private String mKeyAlgorithm;
private String mCharsetName;
private String mSecretKeyType;
private String mDigestAlgorithm;
private String mSecureRandomAlgorithm;
private SecureRandom mSecureRandom;
private IvParameterSpec mIvParameterSpec;
static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA-256")
.setAlgorithm("AES/GCM/NoPadding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
private EncryptionUtil build() throws NoSuchAlgorithmException {
setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
SecureRandom secureRandom = new SecureRandom();
byte[] iv = getIv();
secureRandom.nextBytes(iv);
setIvParameterSpec(new IvParameterSpec(iv));
return new EncryptionUtil(this);
}
private String getCharsetName() {
return mCharsetName;
}
private Builder setCharsetName(String charsetName) {
mCharsetName = charsetName;
return this;
}
private String getAlgorithm() {
return mAlgorithm;
}
private Builder setAlgorithm(String algorithm) {
mAlgorithm = algorithm;
return this;
}
private String getKeyAlgorithm() {
return mKeyAlgorithm;
}
private Builder setKeyAlgorithm(String keyAlgorithm) {
mKeyAlgorithm = keyAlgorithm;
return this;
}
private String getSecretKeyType() {
return mSecretKeyType;
}
private Builder setSecretKeyType(String secretKeyType) {
mSecretKeyType = secretKeyType;
return this;
}
private String getSalt() {
return mSalt;
}
private Builder setSalt(String salt) {
mSalt = salt;
return this;
}
private String getKey() {
return mKey;
}
private Builder setKey(String key) {
mKey = key;
return this;
}
private int getKeyLength() {
return mKeyLength;
}
Builder setKeyLength(int keyLength) {
mKeyLength = keyLength;
return this;
}
private int getIterationCount() {
return mIterationCount;
}
Builder setIterationCount(int iterationCount) {
mIterationCount = iterationCount;
return this;
}
private String getSecureRandomAlgorithm() {
return mSecureRandomAlgorithm;
}
Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
mSecureRandomAlgorithm = secureRandomAlgorithm;
return this;
}
private byte[] getIv() {
return mIv;
}
Builder setIv(byte[] iv) {
mIv = iv;
return this;
}
private SecureRandom getSecureRandom() {
return mSecureRandom;
}
Builder setSecureRandom(SecureRandom secureRandom) {
mSecureRandom = secureRandom;
return this;
}
private IvParameterSpec getIvParameterSpec() {
return mIvParameterSpec;
}
Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
mIvParameterSpec = ivParameterSpec;
return this;
}
private String getDigestAlgorithm() {
return mDigestAlgorithm;
}
Builder setDigestAlgorithm(String digestAlgorithm) {
mDigestAlgorithm = digestAlgorithm;
return this;
}
}
public static void main(String[] args) {
String secretKey = "some_secret_key";
String salt = "some_secret_salt";
EncryptionUtil encryptionUtil = EncryptionUtil.getDefault(secretKey, salt, new byte[12]);
String data = "Data to encrypt";
System.out.println("Encrypted:");
String encrypted = encryptionUtil.encryptOrNull(data);
System.out.println(encrypted);
System.out.println("Decrypted:");
System.out.println(encryptionUtil.decryptOrNull(encrypted));
}
}
请注意我只需要加密数据的帮助
JS代码-
import * as crypto from 'crypto';
export const encData = () => {
const data = 'Data to encrypt';
const secretKey = 'some_secret_key';
const salt = 'some_secret_salt';
let key = '';
const keyHash = key => {
const hash = crypto.createHash('sha256');
const hashedKey = hash.update(key, 'utf-8');
return hashedKey.digest('hex').toUpperCase();
};
const getSecretKey = key => {
return crypto.pbkdf2Sync(key, salt, 1, 16, 'sha1');
};
key = getSecretKey(keyHash(secretKey));
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
const buffer = Buffer.from(_.isPlainObject(data) ? JSON.stringify(data) : data);
// Updating text
let encrypted = cipher.update(buffer);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
};
console.log(encData());
为了确保我的代码工作正常,我正在解密用 JS 函数生成的编码字符串,方法是将它传递给 Java 解密函数。
在Java代码中,加密后的结果组成如下:
iv-length (4 bytes, BE) | IV | ciphertext | authentication tag
相比之下,在NodeJS代码中,结果仅由密文组成,即IV长度,IV和标签缺失,必须添加。
这里必须考虑到 Java 的 SunJCE 提供程序会自动连接密文和标签,而这必须在 NodeJS 代码中明确发生。
此外,密文在 Java 代码中以十六进制编码返回,而在 NodeJS 代码中是 Base64 编码。这也需要在 NodeJS 代码中更改。
修复方法是在 NodeJS 代码中替换以下行:
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
与:
const length = Buffer.allocUnsafe(4);
length.writeUInt32BE(iv.length);
// Using concatenation
encrypted = Buffer.concat([length, iv, encrypted, cipher.final(), cipher.getAuthTag()]);
return encrypted.toString('hex');
有了这个,NodeJS代码returns一个可以被Java代码解密的结果。
请注意静态盐是不安全的。相反,盐应该像每次加密的 IV 一样随机生成,并与密文一起传递。
此外,迭代次数为 1 是不安全的,该值应尽可能高且性能可接受。
在派生 PBKDF2 之前使用 SHA256 散列密钥实际上是不必要的(至少如果 PBKDF2 被正确应用)。
我们在我们的组织中使用第三方的服务,我们必须以加密方式向他们发送一些数据。最近,他们将加密算法更新为 AES/GCM/NoPadding.
他们在 java 中有他们的代码,而我们使用 java 脚本。他们在 Java 中与我们分享了算法的实现,我们必须在 JS 中复制和实现该算法,因为这就是我们使用的。
我在转换此代码时遇到了挑战。附上两个 Java 实现,它像一个魅力一样工作,并且 JS 代码没有按预期工作。虽然我尝试了很多东西,但其中 none 对我有用。所以,我只分享我尝试过的最新代码。
我不了解 Java 或密码学,因此非常感谢这方面的任何帮助。
JAVA代码-
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption class for managing all types of AES encryptions
*/
public class EncryptionUtil {
private final Builder mBuilder;
private final static String HEX = "0123456789ABCDEF";
private EncryptionUtil(Builder builder) {
mBuilder = builder;
}
public static EncryptionUtil getDefault(String key, String salt, byte[] iv) {
try {
return Builder.getDefaultBuilder(key, salt, iv).build();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
public String encryptOrNull(String data) {
try {
return encrypt(data);
} catch (Exception e) {
return "";
}
}
private String encrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doEncryptAES(data, secretKey, mBuilder.getAlgorithm(), mBuilder.getCharsetName());
}
private String decrypt(String data) throws Exception {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
return doDecryptAES(data, secretKey, mBuilder.getAlgorithm());
}
private String doEncryptAES(String inputString,
SecretKey key, String xForm, String charset) throws Exception {
byte inpBytes[] = inputString.getBytes(charset);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, mBuilder.getIv()));
byte[] encryptedData = cipher.doFinal(inpBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + mBuilder.getIv().length + encryptedData.length);
byteBuffer.putInt(mBuilder.getIv().length);
byteBuffer.put(mBuilder.getIv());
byteBuffer.put(encryptedData);
return toHex(byteBuffer.array());
}
return toHex(cipher.doFinal(inpBytes));
}
/**
* for AES in GCM mode kitkat version is required
*
* @param inputString is String we want to decrypt
* @param key is symmetric key use for decryption and it similar to key used for encryption (128,192,256)
* @param xForm is the transformation form in which form we want to transform
* (AES/ECB/PKCS5Padding,AES/ECB/NoPadding,AES/CBC/PKCS5Padding,AES/CBC/NoPadding,AES/GCM/NoPadding)
* @return it reurn decrypted string
* @throws Exception NOSuchAlgorithmEXception,NoSuchPaddingEXception
*/
private String doDecryptAES(String inputString,
SecretKey key, String xForm) throws Exception {
byte[] inpBytes = toByte(inputString);
Cipher cipher = Cipher.getInstance(xForm);
switch (xForm) {
case "AES/ECB/PKCS5Padding":
case "AES/ECB/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key);
break;
case "AES/CBC/PKCS5Padding":
case "AES/CBC/NoPadding":
cipher.init(Cipher.DECRYPT_MODE, key, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
break;
case "AES/GCM/NoPadding":
ByteBuffer byteBuffer = ByteBuffer.wrap(inpBytes);
int noonceSize = byteBuffer.getInt();
if (noonceSize < 12 || noonceSize >= 16)
throw new IllegalArgumentException("Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file.");
byte[] iv = new byte[noonceSize];
byteBuffer.get(iv);
byte[] cipherBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
return new String(cipher.doFinal(cipherBytes), mBuilder.getCharsetName());
}
return new String(cipher.doFinal(inpBytes), mBuilder.getCharsetName());
}
public String decryptOrNull(String data) {
try {
return decrypt(data);
} catch (Exception e) {
return "";
}
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return toHex(messageDigest.digest()).toCharArray();
}
private byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
return result;
}
public String toHex(byte[] stringBytes) {
StringBuffer result = new StringBuffer(2 * stringBytes.length);
for (int i = 0; i < stringBytes.length; i++) {
result.append(HEX.charAt((stringBytes[i] >> 4) & 0x0f)).append(HEX.charAt(stringBytes[i] & 0x0f));
}
return result.toString();
}
private static class Builder {
private byte[] mIv;
private int mKeyLength;
private int mIterationCount;
private String mSalt;
private String mKey;
private String mAlgorithm;
private String mKeyAlgorithm;
private String mCharsetName;
private String mSecretKeyType;
private String mDigestAlgorithm;
private String mSecureRandomAlgorithm;
private SecureRandom mSecureRandom;
private IvParameterSpec mIvParameterSpec;
static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA-256")
.setAlgorithm("AES/GCM/NoPadding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
private EncryptionUtil build() throws NoSuchAlgorithmException {
setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
SecureRandom secureRandom = new SecureRandom();
byte[] iv = getIv();
secureRandom.nextBytes(iv);
setIvParameterSpec(new IvParameterSpec(iv));
return new EncryptionUtil(this);
}
private String getCharsetName() {
return mCharsetName;
}
private Builder setCharsetName(String charsetName) {
mCharsetName = charsetName;
return this;
}
private String getAlgorithm() {
return mAlgorithm;
}
private Builder setAlgorithm(String algorithm) {
mAlgorithm = algorithm;
return this;
}
private String getKeyAlgorithm() {
return mKeyAlgorithm;
}
private Builder setKeyAlgorithm(String keyAlgorithm) {
mKeyAlgorithm = keyAlgorithm;
return this;
}
private String getSecretKeyType() {
return mSecretKeyType;
}
private Builder setSecretKeyType(String secretKeyType) {
mSecretKeyType = secretKeyType;
return this;
}
private String getSalt() {
return mSalt;
}
private Builder setSalt(String salt) {
mSalt = salt;
return this;
}
private String getKey() {
return mKey;
}
private Builder setKey(String key) {
mKey = key;
return this;
}
private int getKeyLength() {
return mKeyLength;
}
Builder setKeyLength(int keyLength) {
mKeyLength = keyLength;
return this;
}
private int getIterationCount() {
return mIterationCount;
}
Builder setIterationCount(int iterationCount) {
mIterationCount = iterationCount;
return this;
}
private String getSecureRandomAlgorithm() {
return mSecureRandomAlgorithm;
}
Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
mSecureRandomAlgorithm = secureRandomAlgorithm;
return this;
}
private byte[] getIv() {
return mIv;
}
Builder setIv(byte[] iv) {
mIv = iv;
return this;
}
private SecureRandom getSecureRandom() {
return mSecureRandom;
}
Builder setSecureRandom(SecureRandom secureRandom) {
mSecureRandom = secureRandom;
return this;
}
private IvParameterSpec getIvParameterSpec() {
return mIvParameterSpec;
}
Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
mIvParameterSpec = ivParameterSpec;
return this;
}
private String getDigestAlgorithm() {
return mDigestAlgorithm;
}
Builder setDigestAlgorithm(String digestAlgorithm) {
mDigestAlgorithm = digestAlgorithm;
return this;
}
}
public static void main(String[] args) {
String secretKey = "some_secret_key";
String salt = "some_secret_salt";
EncryptionUtil encryptionUtil = EncryptionUtil.getDefault(secretKey, salt, new byte[12]);
String data = "Data to encrypt";
System.out.println("Encrypted:");
String encrypted = encryptionUtil.encryptOrNull(data);
System.out.println(encrypted);
System.out.println("Decrypted:");
System.out.println(encryptionUtil.decryptOrNull(encrypted));
}
}
请注意我只需要加密数据的帮助
JS代码-
import * as crypto from 'crypto';
export const encData = () => {
const data = 'Data to encrypt';
const secretKey = 'some_secret_key';
const salt = 'some_secret_salt';
let key = '';
const keyHash = key => {
const hash = crypto.createHash('sha256');
const hashedKey = hash.update(key, 'utf-8');
return hashedKey.digest('hex').toUpperCase();
};
const getSecretKey = key => {
return crypto.pbkdf2Sync(key, salt, 1, 16, 'sha1');
};
key = getSecretKey(keyHash(secretKey));
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
const buffer = Buffer.from(_.isPlainObject(data) ? JSON.stringify(data) : data);
// Updating text
let encrypted = cipher.update(buffer);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
};
console.log(encData());
为了确保我的代码工作正常,我正在解密用 JS 函数生成的编码字符串,方法是将它传递给 Java 解密函数。
在Java代码中,加密后的结果组成如下:
iv-length (4 bytes, BE) | IV | ciphertext | authentication tag
相比之下,在NodeJS代码中,结果仅由密文组成,即IV长度,IV和标签缺失,必须添加。
这里必须考虑到 Java 的 SunJCE 提供程序会自动连接密文和标签,而这必须在 NodeJS 代码中明确发生。
此外,密文在 Java 代码中以十六进制编码返回,而在 NodeJS 代码中是 Base64 编码。这也需要在 NodeJS 代码中更改。
修复方法是在 NodeJS 代码中替换以下行:
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
与:
const length = Buffer.allocUnsafe(4);
length.writeUInt32BE(iv.length);
// Using concatenation
encrypted = Buffer.concat([length, iv, encrypted, cipher.final(), cipher.getAuthTag()]);
return encrypted.toString('hex');
有了这个,NodeJS代码returns一个可以被Java代码解密的结果。
请注意静态盐是不安全的。相反,盐应该像每次加密的 IV 一样随机生成,并与密文一起传递。
此外,迭代次数为 1 是不安全的,该值应尽可能高且性能可接受。
在派生 PBKDF2 之前使用 SHA256 散列密钥实际上是不必要的(至少如果 PBKDF2 被正确应用)。