DEV Community

loading...

Fullstack AES-GCM encryption-decryption in node.js and the client-side

shahinghasemi profile image shahinghasemi ・2 min read

TL;DR
You can find the fully workable gist code here.

AES(Advanced Encryption Standard) is a symmetric kind of cryptographic method which has different modes that you can read further here.
Without any further let’s get to the point. I wanted to encrypt some messages on the server side (Node.js) and send that encrypted message back to the client-side which can be pretty anything, e.g. React, Vue, Angular, vanilla JS, etc.
Since cryptographic stuff is sensitive, I wanted to do so using Native APIs that browser offers out of the box but I occurred some weird problems due to lack of enough resources for AES-GCM method, but finally I go the solution.
This is the code that should be run on the server side (node.js)

function encrypt(message){
  const KEY = crypto.randomBytes(32)
  const IV = crypto.randomBytes(16)
  const ALGORITHM = 'aes-256-gcm';

  const cipher = crypto.createCipheriv(ALGORITHM, KEY, IV);
  let encrypted = cipher.update(message, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const tag = cipher.getAuthTag()

  let output = {
    encrypted,
    KEY: KEY.toString('hex'),
    IV: IV.toString('hex'),
    TAG: tag.toString('hex'),
  }
  return output;
}
Enter fullscreen mode Exit fullscreen mode

And this is the code that should be run on the client side (browser)

function decrypt() {
  let KEY = hexStringToArrayBuffer(data.KEY);
  let IV = hexStringToArrayBuffer(data.IV);
  let encrypted = hexStringToArrayBuffer(data.encrypted + data.TAG);

  window.crypto.subtle.importKey('raw', KEY, 'AES-GCM', true, ['decrypt']).then((importedKey)=>{
    console.log('importedKey: ', importedKey);
    window.crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv: IV,
      },
      importedKey,
      encrypted
    ).then((decodedBuffer)=>{
      let plaintext = new TextDecoder('utf8').decode(decodedBuffer);
      console.log('plainText: ', plaintext);
    })
 })

function hexStringToArrayBuffer(hexString) {
  hexString = hexString.replace(/^0x/, '');
  if (hexString.length % 2 != 0) {
    console.log('WARNING: expecting an even number of characters in the hexString');
  }
  var bad = hexString.match(/[G-Z\s]/i);
  if (bad) {
      console.log('WARNING: found non-hex characters', bad);    
  }
  var pairs = hexString.match(/[\dA-F]{2}/gi);
  var integers = pairs.map(function(s) {
      return parseInt(s, 16);
  });
  var array = new Uint8Array(integers);
  return array.buffer;
} 
Enter fullscreen mode Exit fullscreen mode

I think the code is expressive enough but the take away is we should concatenate the encrypted and authentication tag together and passed it to the decrypt method of the subtle crypto API.

If you've got any question, let me know on the comments ;)

Discussion (0)

pic
Editor guide