Aes-256-cbc decode example

Hello!
I have working encoder/decoder on my php backend with key and vector in plain text.
Trying to create a simple POC decoder to pass some secret info from my backend to CF worker via client request.
Do you guys have a working example how to decode simple string in my case?
Thanks!

Hi, not sure if this is what you are after but wrote a small wrapper for a current project.

I’m concatenating the iv with the as hex with the base64 encoded buffer if I remember correctly which isn’t that pretty. So, probably want to change it a bit but hopefully it’s good enough for insperation.

const hexKey = <KEY>
let key;

function hexStringToUint8Array(hexString) {
  const arrayBuffer = new Uint8Array(hexString.length / 2);

  for (let i = 0; i < hexString.length; i += 2) {
    const byteValue = parseInt(hexString.substr(i, 2), 16);
    arrayBuffer[i / 2] = byteValue;
  }

  return arrayBuffer;
}

function base64ToArraybuffer(base64) {
  const binary = atob(base64.replace(/-/g, '+').replace(/_/g, '/'));
  const len = binary.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes.buffer;
}

function arraybufferToString(buf) {
  return String.fromCharCode.apply(null, new Uint8Array(buf));
}

async function getKey() {
  if (key) {
    return key;
  }

  const keyBuffer = hexStringToUint8Array(hexKey);
  key = await crypto.subtle.importKey(
    'raw',
    keyBuffer,
    {
      name: 'AES-CBC',
    },
    false,
    ['decrypt'],
  );

  return key;
}

async function decrypt(data) {
  const key = await getKey();

  // Slice the iv from the data
  const nonce = hexStringToUint8Array(data.slice(0, 32));

  try {
    const dataBuffer = base64ToArraybuffer(data.slice(32));
    const decrypted = await crypto.subtle.decrypt(
      {
        name: 'AES-CBC',
        iv: nonce,
      },
      key,
      dataBuffer,
    );

    return JSON.parse(arraybufferToString(decrypted));
  } catch (err) {
    console.log(`Decryption failed: ${err.message}`);
    throw err;
  }
}

module.exports = {
  decrypt,
};

Well, I’ve tried to reuse some of your functions and still can’t make it work.
My php functions looks like this and I can’t change it unfortunately:

   function aes_encrypt($data, $vector = null) {
          return $data ? bin2hex(base64_decode(openssl_encrypt($data, 'aes-256-cbc', ENCRYPTION_KEY, null, md5(md5($vector ?? ENCRYPTION_KEY), true)))) : false;
   }
   function aes_decrypt($data, $vector = null) {
          $data = @pack('H*', $data);
          $data = base64_encode($data);
          return openssl_decrypt($data, 'aes-256-cbc', ENCRYPTION_KEY, null, md5(md5($vector ?? ENCRYPTION_KEY), true));
   }

So, I’ve recreated pack('H*') with this function (works fine, tested):

function pack_hex(source) {
    var source = source.length % 2 ? source + '0' : source,
        result = '';
    for( var i = 0; i < source.length; i = i + 2 ) result += String.fromCharCode( parseInt( source.substr( i , 2 ), 16 ) );
    return result;
}

and my decrypt function still triggers some errors in CF console (“Error: internal error” at crypto.subtle.decrypt line):

async function decrypt(data, key, iv) {
    data = pack_hex(data);
    data = btoa(data); // DOESN'T HELP HERE
    data = base64ToArraybuffer(data); // stringToArray() DOESN'T WORK TOO
    const decypher = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: iv }, key, data);
    return arrayToString(decypher);
}

My PHP skills are unfortunately no good so I won’t be able to help there… What I can share is the corresponding node.js code to encrypt if that’s of any help:
const crypto = require(‘crypto’);

const algorithm = 'aes-256-cbc';
const hexKey = 'key';
const key = crypto.createSecretKey(hexStringToUint8Array(hexKey));

/**
 * Helper function to validate the the encryption works on node side..
 */
function hexStringToUint8Array(hexString) {
  if (hexString.length % 2 !== 0) {
    throw 'Invalid hexString';
  }

  const arrayBuffer = new Uint8Array(hexString.length / 2);

  for (let i = 0; i < hexString.length; i += 2) {
    const byteValue = parseInt(hexString.substr(i, 2), 16);
    if (isNaN(byteValue)) {
      throw 'Invalid hexString';
    }
    arrayBuffer[i / 2] = byteValue;
  }

  return arrayBuffer;
}

function encrypt(text) {
  const nonce = crypto.randomBytes(16);

  const cipher = crypto.createCipheriv(algorithm, key, nonce);
  let crypted = cipher.update(text, 'utf8', 'base64');
  crypted += cipher.final('base64');

  // Stick the nonce in the beginning of the encrypted message
  const cryptedAndNonce = nonce.toString('hex') + crypted;
  // Make it url friendly
  return cryptedAndNonce
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

function decrypt(text) {
  const nonce = hexStringToUint8Array(text.slice(0, 32));

  const decipher = crypto.createDecipheriv(algorithm, key, nonce);
  let dec = decipher.update(text.slice(32), 'base64', 'utf8');
  dec += decipher.final('utf8');
  return dec;
}

module.exports = {
  decrypt,
  encrypt,
};

And… the key needs to be the right length. I create the keys like this:

const crypto = require(‘crypto’);

const key = crypto.createSecretKey(crypto.randomBytes(32));

console.log(key.export().toString(‘hex’));