UNPKG

@planq-network/encrypted-backup

Version:

Libraries for implemented password encrypted account backups

185 lines 8.18 kB
"use strict"; 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