var crypto = require('crypto');

// larger numbers mean better security, less
var config = {
  digest: 'sha512',
  // size of the generated hash
  hashBytes: 32,
  // larger salt means hashed passwords are more resistant to rainbow table, but
  // you get diminishing returns pretty fast
  saltBytes: 24,
  // more iterations means an attacker has to take longer to brute force an
  // individual password, so larger is better. however, larger also means longer
  // to hash the password. tune so that hashing the password takes about a
  // second
  iterations: 233335
};

/**
 * Hash a password using Node's asynchronous pbkdf2 (key derivation) function.
 *
 * Returns a self-contained buffer which can be arbitrarily encoded for storage
 * that contains all the data needed to verify a password.
 *
 * @param {!String} password
 * @param {!function(?Error, ?Buffer=)} callback
 */
function hashPassword (password, callback) {
  // generate a salt for pbkdf2
  crypto.randomBytes(config.saltBytes, function (err, salt) {
    if (err) {
      return callback(err);
    }

    crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest,
      function (err, hash) {
        if (err) {
          return callback(err);
        }

        var combined = Buffer.alloc((hash.length + salt.length + 8));

        // include the size of the salt so that we can, during verification,
        // figure out how much of the hash is salt
        combined.writeUInt32BE(salt.length, 0, true);
        // similarly, include the iteration count
        combined.writeUInt32BE(config.iterations, 4, true);

        salt.copy(combined, 8);
        hash.copy(combined, salt.length + 8);
        callback(null, combined);
      }
    );
  });
}

/**
 * Verify a password using Node's asynchronous pbkdf2 (key derivation) function.
 *
 * Accepts a hash and salt generated by hashPassword, and returns whether the
 * hash matched the password (as a boolean).
 *
 * @param {!String} password
 * @param {!Buffer} combined Buffer containing hash and salt as generated by
 *   hashPassword.
 * @param {!function(?Error, !boolean)}
 */
function verifyPassword (password, combined, callback) {
  // extract the salt and hash from the combined buffer
  var saltBytes = combined.readUInt32BE(0);
  var hashBytes = combined.length - saltBytes - 8;
  var iterations = combined.readUInt32BE(4);
  var salt = combined.slice(8, saltBytes + 8);
  var hash = combined.toString('binary', saltBytes + 8);

  // verify the salt and hash against the password
  crypto.pbkdf2(password, salt, iterations, hashBytes, config.digest, function(err, verify) {
    if (err && typeof callback === 'function') {
      return callback(err, false);
    } else if (err) {
      return false;
    }

    if (typeof callback === 'function') {
      callback(null, verify.toString('binary') === hash);
    } else {
      return true;
    }
  });
}

exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;