Search code examples
javascriptjavanode.jspbkdf2aes-gcm

Rewriting Java AES 256 GCM Encryption in Node.js (PBKDF2WithHmacSHA1)


I have the following code in java for encrypting the plain text :

private static final String SECRET_KEY = "SecKeyTest";
private static final String SALT = "thisIsSalt";



public String encrypt(String strToEncrypt) {
    try {
        byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT.getBytes(), 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
        
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128 , iv);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
        return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)));
    } catch (Exception e) {
        System.out.println("Error while encrypting: " + e.toString());
    }
    return null;
}

I have to rewrite the same using NodeJs , what i have did so far :

const salt = "thisIsSalt";
const digest = 'sha256';
const aesSecretKey = "SecKeyTest";

module.exports = {

    encrypt: function (plainText){

        const key = crypto.pbkdf2Sync(aesSecretKey, salt, 65536, 32, digest); //key len 32bytes i.e 256bits
        const iv = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]);

        // AES 256 GCM Mode
        var cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

        // encrypt the given text
        var encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);

        // extract the auth tag
        var tag = cipher.getAuthTag();

        // generate output
        return Buffer.concat([Buffer.from(salt), iv, tag, encrypted]).toString('base64');
        
    }


};

For input: "hello" :

Java: rgCx2SDSqio15M+0lViNAzW/lUmz

Node: dGhpc0lzU2FsdAAAAAAAAAAAAAAAAAAAAADSqio15M+0lViNAzW/lUmzrgCx2SA=


Solution

  • The Java code implicitly concatenates ciphertext and tag in this order. So to get the same result in the NodeJS code, the following change would be necessary:

    return Buffer.concat([encrypted, tag]).toString('base64');
    

    However, salt and IV should not be static, but should be randomly generated for each key derivation and encryption. Since salt and IV are needed for decryption and both are not secret, they are passed along with the ciphertext and tag, typically also concatenated, e.g.: salt | iv | ciphertext | tag.