I am trying to make an API in node.js which decrypts the input that is created by using AES-GCM-256 algo, I am using the same algo in JAVA to encrypt the code but i am not able to decrypt it using node.js
I Have tried many approaches but i am stuck maybe on the tag part & i am getting error 'Unsupported state or unable to authenticate data'
My Java code :
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES256GCMAlgo {
static String plainText = "This is a plain text which need to be encrypted by Java AES 256 GCM Encryption Algorithm";
public static final int AES_KEY_SIZE = 256;
public static final int GCM_IV_LENGTH = 12;
public static final int GCM_TAG_LENGTH = 16;
public static void main(String[] args) throws Exception
{
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(AES_KEY_SIZE);
// Generate Key
SecretKey key = keyGenerator.generateKey();
byte[] IV = new byte[GCM_IV_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);
byte[] encoded = key.getEncoded();
String output = Base64.getEncoder().withoutPadding().encodeToString(encoded);
System.out.println("Keep it secret, keep it safe! " + output);
String ivoutput = Base64.getEncoder().withoutPadding().encodeToString(IV);
System.out.println("Keep ivoutput secret, keep it safe! " + ivoutput);
System.out.println("Original Text : " + plainText);
byte[] cipherText = encrypt(plainText.getBytes(), key, IV);
byte[] tagVal = Arrays.copyOfRange(cipherText, cipherText.length - (128 / Byte.SIZE), cipherText.length);
System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText));
System.out.println("Tag Text : " + Base64.getEncoder().encodeToString(tagVal));
String input = output ;
byte[] deencoded = Base64.getDecoder().decode(output);
SecretKey aesKey = new SecretKeySpec(deencoded, "AES");
String ivinput = ivoutput;
byte[] ivdeencoded = Base64.getDecoder().decode(ivinput);
String decryptedText = decrypt(cipherText, aesKey, ivdeencoded);
System.out.println("DeCrypted Text : " + decryptedText);
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
}
My Node.js code
const crypto = require('crypto');
// input created by running above program in java
const ed = 'OGtANbvTLY6Cme2VNAxsiIhBLLwl29oVX7zC5DGmmq4hU/VqNKaGQuSp1Q8liQ94cW/B96OJoJJ2r67jRlQFI4qHCTWFU2qQ8QaNj6WehdVLsf5mDK2aMYjc/vXd1ha/cElMBzFaIp9g==='
const key = 'HuzPEZgzqKOo8VwlnYhNUaPWTWSVDRQ2bMtY6aJAp8I'
const iv = 'kg5ILA0826hrew5w'
const tag = 'jc/vXd1ha/cElMBzFaIp9g==' // last 16 bytes extracted in java
function decrypt(encrypted, ik, iiv, it) {
let bData = Buffer.from(encrypted, 'base64');
// console.log(bData.length,bData.length - 64)
let tag1 = Buffer.from(tag, 'base64');
// let tag1 = bData.slice((bData.length - 16),bData.length) // also tried slicing last 16 bytes of buffer
console.log('00000000',tag1.length)
let iv1 = Buffer.from(iiv, 'base64');
let key1 = new Buffer(ik, 'base64');
console.log('aaaaaaaaa')
let decipher = crypto.createDecipheriv('aes-256-gcm', key1, iv1)
console.log('bbbbbbbbbbbbb')
decipher.setAuthTag(tag1);
console.log('ccccccc')
let dec = decipher.update(encrypted, 'binary', 'utf8')
dec += decipher.final('utf8');
return dec;
}
console.log('devryptedddddd',decrypt(ed,key,iv,tag))
i should get 'This is a plain text which need to be encrypted by Java AES 256 GCM Encryption Algorithm' in the console of node.js but i am getting 'Unsupported state or unable to authenticate data' error. kindly help.
The 'ed' you use is not the output of that Java code for that plaintext, key, and IV. The value I get is in base64
OGtANbvTLY6Cme2VNAxsiIhBLLwl29oVX7zC5DGmmq4hU/VqNKaGQuSp1Q8liQ94cW/B96OJoJJ2r67jRlQFI4qHCTWFU2qQ8QaNj6WehdVLsf5mDK2aMY3P713dYWv3BJTAcxWiKfY=
(the last 22 chars are different). But that value is not the correct value to use in nodejs; Java crypto returns the GCM tag as the last N bytes of the ciphertext, and you correctly copied it from there to a separate variable, but you didn't remove it from the ciphertext. The correct ciphertext to use in nodejs is in base64:
OGtANbvTLY6Cme2VNAxsiIhBLLwl29oVX7zC5DGmmq4hU/VqNKaGQuSp1Q8liQ94cW/B96OJoJJ2r67jRlQFI4qHCTWFU2qQ8QaNj6WehdVLsf5mDK2aMQ==
(20 chars shorter and last 3 chars different).
Finally, your nodejs does bData = Buffer.from(encrypted, 'base64')
but then ignores bData
and does decipher.update(encrypted, 'binary', 'utf8')
-- using the base64 string as binary, which it isn't. With those two changes:
const crypto = require('crypto');
const ed = 'OGtANbvTLY6Cme2VNAxsiIhBLLwl29oVX7zC5DGmmq4hU/VqNKaGQuSp1Q8liQ94cW/B96OJoJJ2r67jRlQFI4qHCTWFU2qQ8QaNj6WehdVLsf5mDK2aMQ=='
const key = 'HuzPEZgzqKOo8VwlnYhNUaPWTWSVDRQ2bMtY6aJAp8I'
const iv = 'kg5ILA0826hrew5w'
const tag = 'jc/vXd1ha/cElMBzFaIp9g==' // last 16 bytes extracted in java
function decrypt(encrypted, ik, iiv, it) {
let bData = Buffer.from(encrypted, 'base64');
let tag1 = Buffer.from(tag, 'base64');
let iv1 = Buffer.from(iiv, 'base64');
let key1 = new Buffer(ik, 'base64');
let decipher = crypto.createDecipheriv('aes-256-gcm', key1, iv1)
decipher.setAuthTag(tag1);
let dec = decipher.update(bData, 'utf8')
dec += decipher.final('utf8');
return dec;
}
console.log(decrypt(ed,key,iv,tag))
I get the correct output, but also a warning that new Buffer()
(used for key1
) is deprecated; Buffer.from
as you used for the other variables is now preferred.