Leif Ershag
Leif Ershag

Reputation: 166

Trouble reproducing SubtleCrypto in Crypto

I have some code utilizing SubtleCrypto that encrypts a key and stores it in a database.

For another functionality i have to be able to encrypt it in Node 12. Without SubtleCrypto i have to recreate the functionality in Crypto.

I get output of the same size, but it dosen't seem to be decryptable by SubtleCrypto and i'm trying to figure out where i'm going wrong.

This is the code running with SubtleCrypto on browsers:

key = getKeyMaterial(password)];
salt = base64ToArraybuffer(optionalSalt)
aesKey = crypto.subtle.deriveKey(
{
    name: constants.algorithms.pbkdf2,
    salt: salt,
    iterations: pbkdf2Iterations,
    hash: { name: constants.hash.sha256 },
},
key,
{
    name: constants.algorithms.aesGcm,
    length: aesWrapKeyBitsLength,
},
true,
['wrapKey', 'unwrapKey']
),
return {
  salt: arraybufferTobase64(salt),
  aesKey: aesKey,
}


function getKeyMaterial(password) {
var enc = new TextEncoder();
crypto.subtle.importKey(
  constants.format.raw,
  enc.encode(password),
  {
    name: constants.algorithms.pbkdf2,
  },
  false,
  ['deriveKey']
)

Without SubtleCrypto, in Node 12, I'm forced to work with the Crypto library.. This is my, current iteration of the, code.

const ivByteLength = 12;

function wrapKey(privateKey, aesKey) {
  const IV = crypto.randomBytes(ivByteLength);
  const ALGORITHM = 'aes-256-gcm';

  const cipher = crypto.createCipheriv(ALGORITHM, aesKey, IV);
  let encrypted = cipher.update(privateKey, undefined, 'binary');
  encrypted += cipher.final('binary');

  const authTag = cipher.getAuthTag();

  encrypted += authTag;
  const output = {
    wrappedKey: arraybufferTobase64(Buffer.from(encrypted, 'ascii')),
    iv: arraybufferTobase64(IV),
  };
  return output;
}

async function deriveAesGcmKey(password, salt) {
  return new Promise((resolve, reject) => {
    crypto.pbkdf2(password, salt, 100000, 32, 'sha256', (err, derivedKey) => {
      if (err) reject(err);
      else resolve(derivedKey);
    });
  });
}

function arraybufferTobase64(buffer) {
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i += 1) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

function base64ToArraybuffer(base64) {
  const binary = atob(base64);
  const len = binary.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i += 1) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

The wrapped key have the same size in both implementations, but the one generated by Node cannot be unwrapped in the browser.

Am i assuming some defaults wrong or something?

Upvotes: 2

Views: 6875

Answers (1)

Aritra Chakraborty
Aritra Chakraborty

Reputation: 12542

NodeJS has something called webcrypto Class inside crypto package, that has subtle crypto implementation.

Example from the Docs:

const { subtle } = require('crypto').webcrypto;

async function digest(data, algorithm = 'SHA-512') {
  const ec = new TextEncoder();
  const digest = await subtle.digest(algorithm, ec.encode(data));
  return digest;
}

Also check out: https://nodejs.org/api/webcrypto.html#webcrypto_class_subtlecrypto

Edit:

As said in the comments for lower version of Node you can use a polyfill like https://www.npmjs.com/package/@peculiar/webcrypto its a webcrypto implementation for Nodejs version higher than Node10

Upvotes: 6

Related Questions