![](/img/trans.png)
[英]AES/GCM/NoPadding encryption in JAVA & decryption in JavaScript
[英]Java's AES/GCM/NoPadding equivalent in JS
我們在我們的組織中使用第三方的服務,我們必須以加密的方式向他們發送一些數據。 最近,他們將加密算法更新為 AES/GCM/NoPadding。
他們在 java 中有他們的代碼,而我們使用 javascript。他們與我們分享了他們在 Java 中的算法實現,我們必須在 JS 中復制和實現,因為這就是我們使用的。
我在轉換此代碼時面臨挑戰。 附加 Java 實現,它像一個魅力一樣工作,並且沒有按預期工作的 JS 代碼。 盡管我嘗試了多種方法,但沒有一種對我有用。 所以,我只分享我嘗試過的最新代碼。
我不了解 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 function 傳遞給 Java decrypt function 來解密我生成的編碼字符串。
在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 代碼返回一個可以被 Java 代碼解密的結果。
請注意,static 鹽是不安全的。 相反,鹽應該像每次加密的 IV 一樣隨機生成,並與密文一起傳遞。
此外,迭代計數為 1 是不安全的,該值應盡可能高且性能可接受。
在派生 PBKDF2 之前使用 SHA256 散列密鑰實際上是不必要的(至少如果 PBKDF2 被正確應用)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.