2

I encrypt some data in NodeJs using below method. It works fine on Nodejs server to encrypt and decrypt.

Input for encryption = "SIYdcYSyiWY5XUqYMrfv31sPF8DSojs1ikghMs4R"

Encrypted Output in NodeJS = "IRhqmwhlFUxS1Zg+Qyr2ykSkAA1cQ5gVts7RIZoULCbYMS/DVsdfTyanGAAu4HkuAZv4ynk+VBPZ6vaTH5xTONfuVRlUavF9X//12v5et5em1ItPYdlOWkYo5IkodpAQvm5Yrm4F5xaRxRU3+TAZzOQm/3YQIlz74X+3V3/aV/K4qFebSLUMZJM9glPWo4O+ISDf76ztiN2ynLyvsivSU/qUuQEtPVEK"

encryptAES = ((text) => {
    // random initialization vector
    const iv = crypto.randomBytes(16);

    const salt = crypto.randomBytes(64);

    var masterKey = crypto.randomBytes(32);

    const key = crypto.pbkdf2Sync(masterKey, salt, 2145, 32, 'sha512');

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

    const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);

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

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

I try to decrypt it on the Java client but for some reason its failing. I am pretty sure that I am not using the correct way to decrypt. Below is how far i got


    public String decrypt(String base64EncryptedData) throws Exception {

        byte[] decryptedData = Base64.getDecoder().decode(base64EncryptedData);

        byte[] salt =  Arrays.copyOfRange(decryptedData, 0, 64); 
        byte[] iv = Arrays.copyOfRange(decryptedData, 64, 80); 
        byte[] tag = Arrays.copyOfRange(decryptedData, 80, 96); 
        String masterKey = new String(Arrays.copyOfRange(decryptedData, 96, 128), "UTF-8"); 
        byte[] text = Arrays.copyOfRange(decryptedData, 128, decryptedData.length); ;
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tag.length * 8, iv);

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        KeySpec spec = new PBEKeySpec(masterKey.toCharArray(), salt, 2145, 32*8);
        SecretKey tmp = factory.generateSecret(spec);
        key = new SecretKeySpec(tmp.getEncoded(), "AES");
        dcipher = Cipher.getInstance("AES/GCM/NoPadding");
        dcipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);

        byte[] utf8 = dcipher.doFinal(text);
        return new String(utf8, "UTF8");
    }


In Java I get exception

javax.crypto.AEADBadTagException: Tag mismatch!

1 Answers1

2

There are several problems in the code:

  • The most serious is that the password is sent with the message. This allows anyone to decrypt the ciphertext. The password must be secret, must not be sent with the message, only the sender and recipient may know it.
  • The key tmp, derived in the Java code with PBKDF2WithHmacSHA512, is an instance of PBKDF2KeyImpl, which stores the password in a char[] whose characters will be UTF8 encoded (see the documentation of the PBKDF2KeyImpl class). In the NodeJS code, however, the password is generated by filling a buffer pseudo-randomly using crypto.randomBytes. Random binary data cannot usually be converted into a string using UTF8 encoding without corrupting the data, here. To avoid this problem, either UTF8 encodable data or BouncyCastle's PKCS5S2ParametersGenerator, which expects a byte[] (in PBEParametersGenerator#init), must be used, depending also on the context in which the KDF is applied (e.g. deriving the key from a password or from a master key, here).
  • In Java, the ciphertext and authentication tag are not processed separately, but as a single byte[], with the tag appended to the end of the ciphertext. In the current Java code, the authentication tag is separated but not used anywhere (except as a reference for its length in the GCMparameterSpec constructor).
  • Also note the recommendations regarding the PBKDF2 parameters, especially regarding the iteration count, RFC8018, sections 4.1 and 4.2 and here.
Community
  • 1
  • 1
Topaco
  • 40,594
  • 4
  • 35
  • 62