简体   繁体   English

JS 中 Java 的 AES/GCM/NoPadding 等价物

[英]Java's AES/GCM/NoPadding equivalent in JS

We are using the services of a third party in our organization in which we have to send some data to them in an encrypted manner.我们在我们的组织中使用第三方的服务,我们必须以加密的方式向他们发送一些数据。 Recently, they updated the encryption algorithm to AES/GCM/NoPadding.最近,他们将加密算法更新为 AES/GCM/NoPadding。

They have their code in java whereas we use javascript. they have shared with us their implementation of the algorithm in Java which we have to replicate and implement in JS because that is what we use.他们在 java 中有他们的代码,而我们使用 javascript。他们与我们分享了他们在 Java 中的算法实现,我们必须在 JS 中复制和实现,因为这就是我们使用的。

I am facing challenges in converting this code.我在转换此代码时面临挑战。 Attaching both Java implementation which works like a charm and the JS code which is not working as expected.附加 Java 实现,它像一个魅力一样工作,并且没有按预期工作的 JS 代码。 Although I have tried multiple things but none of them worked for me.尽管我尝试了多种方法,但没有一种对我有用。 So, I am sharing only the latest code that I tried.所以,我只分享我尝试过的最新代码。

I have no knowledge of Java or cryptography so any help in that direction will be highly appreciated.我不了解 Java 或密码学,因此非常感谢这方面的任何帮助。

JAVA Code - 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));
    }
}

Please note I need help only to encrypt the data请注意我只需要帮助来加密数据

JS Code - 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());

To make sure my code is working fine I am decrypting my encoded string generated with JS function by passing it to Java decrypt function.为了确保我的代码工作正常,我通过将 JS function 传递给 Java decrypt function 来解密我生成的编码字符串。

In the Java code, the result of the encryption is composed as follows:在Java代码中,加密的结果组成如下:

iv-length (4 bytes, BE) | IV | ciphertext | authentication tag 

In contrast, in the NodeJS code the result consists only of the ciphertext, ie IV length, IV and tag are missing and must be added.相反,在 NodeJS 代码中,结果仅由密文组成,即 IV 长度,IV 和标签缺失,必须添加。
Here it must be taken into account that Java's SunJCE provider automatically concatenates ciphertext and tag, while this must happen explicitly in the NodeJS code.这里必须考虑到 Java 的 SunJCE 提供程序会自动连接密文和标签,而这必须在 NodeJS 代码中明确发生。

Also, the ciphertext is returned hex encoded in the Java code, while it is Base64 encoded in the NodeJS code.此外,密文以 Java 代码返回十六进制编码,而在 NodeJS 代码中以 Base64 编码返回。 This also needs to be changed in the NodeJS code.这也需要在 NodeJS 代码中更改。

The fix is to replace in the NodeJS code the lines:解决方法是在 NodeJS 代码中替换以下行:

// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);

return encrypted.toString('base64');

with:和:

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');

With this, the NodeJS code returns a result that can be decrypted by the Java code.有了这个,NodeJS 代码返回一个可以被 Java 代码解密的结果。


Note that a static salt is insecure.请注意,static 盐是不安全的。 Instead, the salt should be randomly generated like the IV for each encryption and passed along with the ciphertext.相反,盐应该像每次加密的 IV 一样随机生成,并与密文一起传递。
Also, an iteration count of 1 is not secure, the value should be as high as possible with acceptable performance.此外,迭代计数为 1 是不安全的,该值应尽可能高且性能可接受。
Hashing the key with SHA256 before the PBKDF2 derivation is actually not necessary (at least if PBKDF2 is applied correctly).在派生 PBKDF2 之前使用 SHA256 散列密钥实际上是不必要的(至少如果 PBKDF2 被正确应用)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM