2

I have encrypted a message using AES/GCM/NoPadding algorithm(AES-256) in java & trying to decrypt it in NodeJs. Getting exception "Error: Unsupported state or unable to authenticate data" while decryption. Below is the complete code of java and nodejs & error message: Pl help me where is the incorrect code in java or nodejs.

Below is the code started with Java encryption code :

 public static String encryptAES(String privateString, String skey) throws Exception{   
    byte[] iv = new byte[GCM_IV_BYTES_LENGTH]; //12 iv length
    byte[] tag = new byte[GCM_TAG_BYTES_LENGTH]; //16 tag length
    (new SecureRandom()).nextBytes(iv);
    (new SecureRandom()).nextBytes(tag);

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); //algorithm type
    GCMParameterSpec ivSpec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * Byte.SIZE, iv);
    cipher.init(Cipher.ENCRYPT_MODE, getKey(skey), ivSpec);

    byte[] ciphertext = cipher.doFinal(privateString.getBytes("UTF8"));

    byte[] ivTag = new byte[GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH]; // merging iv and tag
    System.arraycopy(iv, 0, ivTag, 0, iv.length);
    System.arraycopy(tag, 0, ivTag, iv.length, tag.length);

    byte[] encrypted = new byte[ivTag.length + ciphertext.length]; //merging ivtag and cipher
    System.arraycopy(ivTag, 0, encrypted, 0, ivTag.length);
    System.arraycopy(ciphertext, 0, encrypted, ivTag.length, ciphertext.length);

    String encoded = Base64.getEncoder().encodeToString(encrypted); //b64 encoded value
    System.out.println("encrypted str:>" + encoded.length() + " | " + encoded);
    return encoded;
}

//NodeJS decryption code :

function decryptTokenResponse(encryptedStr){
    let data = encryptedStr
    const bData = Buffer.from(data, 'base64');

    const iv = bData.slice(0, 12);
    const tag = bData.slice(12, 28);
    const text = bData.slice(28);

    var decipher = crypto.createDecipheriv(algorithm,masterkey, iv)
    decipher.setAuthTag(tag)
    var plainText = decipher.update(text,'base64','utf-8');
    plainText += decipher.final('utf-8'); **//getting exception here**
    console.log('Decrypted data = ' + plainText)
}           


**//Error :**

                internal/crypto/cipher.js:145
                  const ret = this._handle.final();
                                           ^

                Error: Unsupported state or unable to authenticate data
                    at Decipheriv.final (internal/crypto/cipher.js:145:28)
                    at decryptTokenResponse (/home/jdoodle.js:40:27)
                    at Object.<anonymous> (/home/jdoodle.js:18:1)
                    at Module._compile (internal/modules/cjs/loader.js:678:30)
                    at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
                    at Module.load (internal/modules/cjs/loader.js:589:32)
                    at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
                    at Function.Module._load (internal/modules/cjs/loader.js:520:3)
                    at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
                    at startup (internal/bootstrap/node.js:228:19)
                Command exited with non-zero status 1
Morteza Jalambadani
  • 1,881
  • 5
  • 19
  • 30
Paddy02
  • 71
  • 11

2 Answers2

1

You must provide the AuthenticationTag to createDeCipherIv() when using AES in GCM, CCM and OCBmodes.

Why would you implement GCM without it? You may as well use CTR mode of AES if you don't want the additional protections.

jas-
  • 1,724
  • 1
  • 17
  • 28
  • Not on `createCipheriv` but instead by calling `setAuthTag` on the `decipher` object -- as OP does. Also, OFB is not an AE mode; ITYM OCB. But Java doesn't do OCB, at least not yet. – dave_thompson_085 Mar 24 '19 at 21:14
  • Thanks. I meant `OFB` and `decryptCipherIv()` – jas- Mar 25 '19 at 00:55
1

The authtag for GCM or CCM is generated by the encrypt operation -- you do not randomly generate it yourself (as you do, or at least can, for the IV/nonce). However it is sort of hidden, because Java crypto fits authenticated encryption into its preexisting API by appending the tag to the ciphertext returned by an encrypt operation, or input to a decrypt operation. OTOH nodejs/OpenSSL treats them as separate values. (Both Java and nodejs/OpenSSL treat AAD as separate, but you aren't using AAD.)

Since you are already packing things together (and base64ing) for transmission, you should:

  • in Java, concatenate the IV plus the return from cipher.doFinal (which is ctx + tag) forming IV + ctx + tag

  • base64 and send and after receiving de-base64 as you already do

  • in nodejs, split these into IV,ctx,tag which is easy because Buffer can slice from both ends: bData.slice(0,12) bData.slice(12,-16) bData.slice(-16)

Also your text is already de-base64-ed, but since it's a Buffer the inputEncoding to decipher.update is ignored.

dave_thompson_085
  • 24,048
  • 4
  • 34
  • 52
  • Thanks for your answer! It worked for me. I was providing encoding in `decipher.final(utf8)` but was using ascii strings instead of buffers for iv & encryptedText in `crypto.createDecipheriv` and `decipher.update` respectively. After changing them to buffers and then using `slice` as you mentioned, I got it working. Thank you! – Vimanyu Dec 08 '20 at 01:53