nickarthur
3/28/2019 - 6:33 AM

Compute key+IV from passphrase (createCipher to createCipheriv migration script)

Compute key+IV from passphrase (createCipher to createCipheriv migration script)

// Copyright (c) 2017, Ben Noordhuis <info@bnoordhuis.nl>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// Compute the key and IV that crypto.createCipher() derives from the
// passphrase.  Use this when migrating to crypto.createCipheriv().
'use strict';

const { createCipheriv, createHash } = require('crypto');

function sizes(cipher) {
  for (let nkey = 1, niv = 0;;) {
    try {
      createCipheriv(cipher, '.'.repeat(nkey), '.'.repeat(niv));
      return [nkey, niv];
    } catch (e) {
      if (/invalid iv length/i.test(e.message)) niv += 1;
      else if (/invalid key length/i.test(e.message)) nkey += 1;
      else throw e;
    }
  }
}

function compute(cipher, passphrase) {
  let [nkey, niv] = sizes(cipher);
  for (let key = '', iv = '', p = '';;) {
    const h = createHash('md5');
    h.update(p, 'hex');
    h.update(passphrase);
    p = h.digest('hex');
    let n, i = 0;
    n = Math.min(p.length-i, 2*nkey);
    nkey -= n/2, key += p.slice(i, i+n), i += n;
    n = Math.min(p.length-i, 2*niv);
    niv -= n/2, iv += p.slice(i, i+n), i += n;
    if (nkey+niv === 0) return [key, iv];
  }
}

const [arg0, arg1, cipher, passphrase] = process.argv;
if (cipher && passphrase !== undefined) {
  const [key, iv] = compute(cipher, passphrase);
  console.log('key:', key);
  console.log('iv: ', iv);
  if (/ccm|ctr|gcm/i.test(cipher)) {
    console.log(`WARNING: Data encrypted with crypto.createCipher('${cipher}')`);
    console.log(`WARNING: is fundamentally compromised.  You should fix this ASAP!`);
  }
} else {
  const [cipher, passphrase] = ['aes-128-cbc', 'secret'];
  const [key, iv] = compute(cipher, passphrase);
  console.log('Usage:');
  console.log(`  ${arg0} ${arg1} CIPHER PASSPHRASE`);
  console.log();
  console.log('Example:');
  console.log(`  ${arg0} ${arg1} ${cipher} ${passphrase}`);
  console.log();
  console.log('Prints:');
  console.log(`  key: ${key}`);
  console.log(`  iv:  ${iv}`);
  console.log();
  console.log('Now update your code and replace this:');
  console.log();
  console.log(`  const cipher = crypto.createCipher('${cipher}', '${passphrase}');`);
  console.log();
  console.log('With this:');
  console.log();
  console.log(`  const key = Buffer.from('${key}', 'hex');`);
  console.log(`  const iv  = Buffer.from('${iv}', 'hex');`);
  console.log(`  const cipher = crypto.createCipheriv('${cipher}', key, iv);`);
}