@planq-network/encrypted-backup
Version:
Libraries for implemented password encrypted account backups
185 lines • 8.18 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.computationalHardenKey = exports.scrypt = exports.pbkdf2 = exports.decrypt = exports.encrypt = exports.deriveKey = exports.KDFInfo = void 0;
var result_1 = require("@planq-network/base/lib/result");
var crypto = __importStar(require("crypto"));
var config_1 = require("./config");
var errors_1 = require("./errors");
/** Info strings to separate distinct usages of the key derivation function */
var KDFInfo;
(function (KDFInfo) {
KDFInfo["PASSWORD"] = "Planq Backup Password and Nonce";
KDFInfo["FUSE_KEY"] = "Planq Backup Fuse Key";
KDFInfo["ODIS_AUTH_KEY"] = "Planq Backup ODIS Request Authorization Key";
KDFInfo["ODIS_KEY_HARDENING"] = "Planq Backup ODIS Key Hardening";
KDFInfo["PBKDF"] = "Planq Backup PBKDF Hardening";
KDFInfo["SCRYPT"] = "Planq Backup scrypt Hardening";
KDFInfo["FINALIZE"] = "Planq Backup Key Finalization";
})(KDFInfo = exports.KDFInfo || (exports.KDFInfo = {}));
/**
* Key derivation function for mixing source keying material.
*
* @remarks This function does not add any hardening to the input keying material. It is used only
* to mix the provided key material sources. It's output should not be used to directly derive a key
* from a password or other low entropy sources.
*
* @param info Fixed string value used for domain separation.
* @param sources An array of keying material source values (e.g. a password and a nonce).
*/
function deriveKey(info, sources) {
// Hash each source keying material component, and the info value, to prevent hashing collisions
// that might result if the variable length data is simply concatenated.
var chunks = __spreadArray([Buffer.from(info, 'utf8')], sources, true).map(function (source) {
var hash = crypto.createHash('sha256');
hash.update(source);
return hash.digest();
});
// NOTE: We would prefer to use HKDF here, but is only available in Node v15 and above.
return crypto.pbkdf2Sync(Buffer.concat(chunks), Buffer.alloc(0), 1, 32, 'sha256');
}
exports.deriveKey = deriveKey;
/**
* AES-256-GCM encrypt the given data with the given 32-byte key.
* Encode the ciphertext as { iv || data || auth tag }
*/
function encrypt(key, data) {
try {
// NOTE: AES-GCM uses a 12-byte nonce. Longer nonces get hashed before use.
var nonce = crypto.randomBytes(12);
var cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
return (0, result_1.Ok)(Buffer.concat([nonce, cipher.update(data), cipher.final(), cipher.getAuthTag()]));
}
catch (error) {
return (0, result_1.Err)(new errors_1.EncryptionError(error));
}
}
exports.encrypt = encrypt;
/**
* AES-256-GCM decrypt the given data with the given 32-byte key.
* Ciphertext should be encoded as { iv || data || auth tag }.
*/
function decrypt(key, ciphertext) {
var len = ciphertext.length;
if (len < 28) {
return (0, result_1.Err)(new errors_1.DecryptionError(new Error("ciphertext is too short: expected at least 28 bytes, but got ".concat(len))));
}
try {
// NOTE: AES-GCM uses a 12-byte nonce. Longer nonces get hashed before use.
var nonce = ciphertext.slice(0, 12);
var ciphertextData = ciphertext.slice(12, len - 16);
var auth = ciphertext.slice(len - 16, len);
var decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(auth);
return (0, result_1.Ok)(Buffer.concat([decipher.update(ciphertextData), decipher.final()]));
}
catch (error) {
return (0, result_1.Err)(new errors_1.DecryptionError(error));
}
}
exports.decrypt = decrypt;
/**
* PBKDF2-SHA256 computational key hardening.
*
* @remarks When possible, a memory hard function such as scrypt should be used instead.
* No salt parameter is provided as the intended use case of this function is to harden a
* key value which is derived from a password but already has the salt mixed in.
*
* @see { @link
* https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback |
* NodeJS crypto.pbkdf2 API }
*
* @param key Key buffer to compute hardening against. Should have a salt or nonce mixed in.
* @param iterations Number of PBKDF2 iterations to execute for key hardening.
*/
function pbkdf2(key, iterations) {
return new Promise(function (resolve) {
crypto.pbkdf2(key, KDFInfo.PBKDF, iterations, 32, 'sha256', function (error, result) {
if (error) {
resolve((0, result_1.Err)(new errors_1.PbkdfError(iterations, error)));
}
resolve((0, result_1.Ok)(result));
});
});
}
exports.pbkdf2 = pbkdf2;
/**
* scrypt computational key hardening.
*
* @remarks No salt parameter is provided as the intended use case of this function is to harden a
* key value which is derived from a password but already has the salt mixed in.
*
* @see { @link
* https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback |
* NodeJS crypto.scrypt API }
*
* @param key Key buffer to compute hardening against. Should have a salt or nonce mixed in.
* @param options Options to control the cost of the scrypt function.
*/
function scrypt(key, options) {
var _a;
// Define the maxmem parameter to be large enough to accommodate the provided options.
// See the Node JS crypto implementation of scrypt for more detail.
var maxmem = Math.max(32 * 1024 * 1024, 128 * options.cost * ((_a = options.blockSize) !== null && _a !== void 0 ? _a : 8));
return new Promise(function (resolve) {
crypto.scrypt(key, KDFInfo.SCRYPT, 32, __assign({ maxmem: maxmem }, options), function (error, result) {
if (error) {
resolve((0, result_1.Err)(new errors_1.ScryptError(options, error)));
}
resolve((0, result_1.Ok)(result));
});
});
}
exports.scrypt = scrypt;
function computationalHardenKey(key, config) {
switch (config.function) {
case config_1.ComputationalHardeningFunction.PBKDF:
return pbkdf2(key, config.iterations);
case config_1.ComputationalHardeningFunction.SCRYPT:
return scrypt(key, config);
}
}
exports.computationalHardenKey = computationalHardenKey;
//# sourceMappingURL=utils.js.map