Skip to content

Instantly share code, notes, and snippets.

@DannyWhyte
Created December 11, 2019 09:40
Show Gist options
  • Save DannyWhyte/ea7d7ff473e0a2ddcf76007348418ccd to your computer and use it in GitHub Desktop.
Save DannyWhyte/ea7d7ff473e0a2ddcf76007348418ccd to your computer and use it in GitHub Desktop.
Cross-Platform AES-GCM-256 Encryption & Decryption using JAVA to encrypt and NODE to decrypt

This snippet is about cross-platform AES-GCM-256 encryption & decryption, Where payload is being encrypted using JAVA and decrypted using NODE

sample config :

{
    "masterKey": "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=",
    "iterations": 2333,
    "keyLength": 32,
    "digest": "sha512"
}

The above mentioned values in iterations and keylength are in bytes which needs to be passed in bits inside java

Java Snippet :

import java.io.ByteArrayOutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;


public class AES256GCMAlgo {


    static String plainText = "sample text to encrypt";

    public static final int GCM_IV_LENGTH = 16;
    public static final int GCM_TAG_LENGTH = 16;
    public static final int ITERATIONS = 2333;
    public static final int KEY_LENGTH = 32;

    public static final String MASTER_KEY = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";

    public static void main(String[] args) throws Exception
    {
        byte[]  salt = getSalt();

        byte[] encodedKey     = generateRandomKeyFromMaster(salt);

        byte[] IV = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(IV);

        SecretKey secretKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");

        byte[] cipherText = encrypt(plainText.getBytes(), secretKey, IV);
        byte[] clippedCipherText =  Arrays.copyOfRange(cipherText, 0, cipherText.length - (128 / Byte.SIZE)  );

        byte[] tagVal = Arrays.copyOfRange(cipherText, cipherText.length - (128 / Byte.SIZE), cipherText.length);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write( salt );
        outputStream.write( IV );
        outputStream.write( tagVal );
        outputStream.write( clippedCipherText );

        byte addedEncyptedVal[] = outputStream.toByteArray();

        //Following us the encrypted value
        System.out.println("Encrypted Value is below: " );
        System.out.println(Base64.getEncoder().encodeToString(addedEncyptedVal));
        
        //You can uncomment and call below method to check the decrypted value
        //String decryptedText = decrypt(cipherText, secretKey, IV);
        //System.out.println("DeCrypted Text : " + decryptedText);
    }

    public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] IV) throws Exception
    {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);

        byte[] cipherText = cipher.doFinal(plaintext);

        return cipherText;
    }

    public static String decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws Exception
    {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);

        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);

        byte[] decryptedText = cipher.doFinal(cipherText);

        return new String(decryptedText);
    }

    public static byte[] generateRandomKeyFromMaster(byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException
    {
        char[] masterKey     = MASTER_KEY.toCharArray();
        PBEKeySpec spec = new PBEKeySpec(masterKey, salt, ITERATIONS, KEY_LENGTH * 8);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        byte[] randomKey = skf.generateSecret(spec).getEncoded();
        return randomKey;
    }

    public static byte[] getSalt() throws NoSuchAlgorithmException
    {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[64];
        sr.nextBytes(salt);
        return salt;
    }
}

On running the above snippet you will get output like this :

Encrypted Value is below: TQ1yKce0sbBLYUI2OeDzS2MOQqnev1s/K8k/Lm81asURNUCu2ykL9i44WGEvHUeQ+MpRW5xHU+iKyeE8FpfrANy1EJIsO0CaDgoAFVohl1ReTDCMlCPlZZxlx7ZNL/7d0oQoQR7bTp+0pF+/FWOiinMs0a1QdA==

(if u don't have java in your machine then u can use this website to run the above snippet https://www.jdoodle.com/online-java-compiler)

Pass the encrypted value in Node.js with the above config as shown in snippet below :

Node Snippet :

const  crypto  =  require('crypto'); 

const  decryptAes256Gcm  = (encdata, cryptoConfigObject) => {
    try {
		   // base64 decoding
			const  bData  =  Buffer.from(encdata, 'base64');
		   // convert data to buffers  
		    const  salt  =  bData.slice(0, 64);
			const  iv  =  bData.slice(64, 80);
			const  tag  =  bData.slice(80, 96);
			const  text  =  bData.slice(96);
		  // derive key using; 32 byte key length
		    const  key  =  crypto.pbkdf2Sync(cryptoConfigObject.masterKey, salt, cryptoConfigObject.iterations, cryptoConfigObject.keyLength, cryptoConfigObject.digest);
		  // AES 256 GCM Mode
		    const  decipher  =  crypto.createDecipheriv('aes-256-gcm', key, iv);
		    decipher.setAuthTag(tag);
		    return  decipher.update(text, 'binary', 'utf8') +  decipher.final('utf8')
	    } catch (err) {
			return  err
		}
}
// ======================== //////// ==========================     

const  cryptoConfig  = {
    masterKey:  'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
    iterations:  2333,
    keyLength:  32,
    digest:  'sha512' 
   }

const  encryptedData  =  'TQ1yKce0sbBLYUI2OeDzS2MOQqnev1s/K8k/Lm81asURNUCu2ykL9i44WGEvHUeQ+MpRW5xHU+iKyeE8FpfrANy1EJIsO0CaDgoAFVohl1ReTDCMlCPlZZxlx7ZNL/7d0oQoQR7bTp+0pF+/FWOiinMs0a1QdA=='

console.log('decrypted data ->', decryptAes256Gcm(encryptedData, cryptoConfig))
// ======================== //////// ==========================

On running the above snippet you will get output like this :

decrypted data -> sample text to encrypt

(if u don't have Node.js in your machine then u can use this website to run the above snippet https://www.jdoodle.com/execute-nodejs-online)

@InclinedScorpio
Copy link

is it working ? Has anyone tried?

@DannyWhyte
Copy link
Author

yes it works.

@anilkumar4412
Copy link

is This AES really secured compared to "client side encryption with RSA public key and server side decryption with RSA private." ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment