"use strict"; /** * This module provides easy, yet strong, facilities for encrypting and decrypting serializable objects. The * ease-of-use comes from the fact that this module is opinionated in its (strong) choice of cryptographic algorithms, * lengths, and iterations that cannot be overriden by its users. * * The module exports a `makeCryptoWith` function that takes a single argument, an `opts` object. This object requires * a property named `encryptionKey` which is a passphrase used by the encryption and decryption algorithms within * this module. The `makeCryptoWith` function returns an object containing two functions, `encrypt` and `decrypt`. * * Both the `encrypt` and `decrypt` functions are inverses of each other and return Promises. That is: * someSerializableObj === await decrypt(await encrypt(someSerializableObj)). */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var crypto = require("crypto"); var IV_LENGTH_IN_BYTES = 12; var SALT_LENGTH_IN_BYTES = 64; var KEY_LENGTH_IN_BYTES = 32; var KEY_ITERATIONS = 10000; var KEY_DIGEST = 'sha512'; var CIPHER_ALGORITHM = 'aes-256-gcm'; var ENCRYPTION_RESULT_ENCODING = 'base64'; function _validateOpts(_a) { var encryptionKey = _a.encryptionKey; if (!encryptionKey) { throw new Error('encryptionKey is required'); } } function _validateAAD(aad) { if (aad == null) { return; } if (typeof aad !== 'string') { throw new Error('AAD must be a string'); } if (aad.length === 0) { throw new Error('AAD cannot be an empty string'); } } function _generateSalt() { return crypto.randomBytes(SALT_LENGTH_IN_BYTES); } function _generateIV() { return crypto.randomBytes(IV_LENGTH_IN_BYTES); } function _generateKeySync(encryptionKey, salt) { if (!Buffer.isBuffer(salt)) { salt = Buffer.from(salt, ENCRYPTION_RESULT_ENCODING); } var key = crypto.pbkdf2Sync(encryptionKey, salt, KEY_ITERATIONS, KEY_LENGTH_IN_BYTES, KEY_DIGEST); if (!Buffer.isBuffer(key)) { return Buffer.from(key, 'binary'); } return key; } function _generateKey(encryptionKey, salt) { if (!Buffer.isBuffer(salt)) { salt = Buffer.from(salt, ENCRYPTION_RESULT_ENCODING); } return new Promise(function (resolve, reject) { crypto.pbkdf2(encryptionKey, salt, KEY_ITERATIONS, KEY_LENGTH_IN_BYTES, KEY_DIGEST, function (err, key) { if (err) { reject(err); return; } if (!Buffer.isBuffer(key)) { key = Buffer.from(key, 'binary'); } resolve(key); }); }); } function _serialize(serializable) { var serializedObj = JSON.stringify(serializable); if (serializedObj === undefined) { throw Error('Object to be encrypted must be serializable'); } return serializedObj; } function encrypt(serializedInput, iv, key, salt, aad) { var cipher = crypto.createCipheriv(CIPHER_ALGORITHM, key, iv); if (aad != null) { cipher.setAAD(Buffer.from(aad, 'utf8')); } var encrypted = Buffer.concat([cipher.update(serializedInput, 'utf8'), cipher.final()]); var tag = cipher.getAuthTag(); return Buffer.concat([salt, iv, tag, encrypted]).toString(ENCRYPTION_RESULT_ENCODING); } function decrypt(key, outputBytes, aad) { var iv = outputBytes.slice(SALT_LENGTH_IN_BYTES, SALT_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES); var tag = outputBytes.slice(SALT_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES, SALT_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES + 16); // Auth tag is always 16 bytes long var text = outputBytes.slice(SALT_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES + 16); var decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, key, iv); decipher.setAuthTag(tag); if (aad != null) { decipher.setAAD(Buffer.from(aad, 'utf8')); } var decrypted = decipher.update(text, undefined, 'utf8') + decipher.final('utf8'); return JSON.parse(decrypted); } function asBuffer(encryptedOutput) { return Buffer.isBuffer(encryptedOutput) ? encryptedOutput : Buffer.from(encryptedOutput, ENCRYPTION_RESULT_ENCODING); } /** * Implmenetation of encrypt() and decrypt() taken from https://gist.github.com/AndiDittrich/4629e7db04819244e843, * which was recommended by @jaymode */ function makeCryptoWith(opts) { _validateOpts(opts); var encryptionKey = opts.encryptionKey; return { encrypt: function (input, aad) { return __awaiter(this, void 0, void 0, function () { var salt, serializedInput, iv, key; return __generator(this, function (_a) { switch (_a.label) { case 0: _validateAAD(aad); salt = _generateSalt(); serializedInput = _serialize(input); iv = _generateIV(); return [4 /*yield*/, _generateKey(encryptionKey, salt)]; case 1: key = _a.sent(); return [2 /*return*/, encrypt(serializedInput, iv, key, salt, aad)]; } }); }); }, decrypt: function (encryptedOutput, aad) { return __awaiter(this, void 0, void 0, function () { var outputBytes, salt, key; return __generator(this, function (_a) { switch (_a.label) { case 0: _validateAAD(aad); outputBytes = asBuffer(encryptedOutput); salt = outputBytes.slice(0, SALT_LENGTH_IN_BYTES); return [4 /*yield*/, _generateKey(encryptionKey, salt)]; case 1: key = _a.sent(); return [2 /*return*/, decrypt(key, outputBytes, aad)]; } }); }); }, encryptSync: function (input, aad) { _validateAAD(aad); var serializedInput = _serialize(input); var iv = _generateIV(); var salt = _generateSalt(); var key = _generateKeySync(encryptionKey, salt); return encrypt(serializedInput, iv, key, salt, aad); }, decryptSync: function (encryptedOutput, aad) { _validateAAD(aad); var outputBytes = asBuffer(encryptedOutput); var salt = outputBytes.slice(0, SALT_LENGTH_IN_BYTES); var key = _generateKeySync(encryptionKey, salt); return decrypt(key, outputBytes, aad); }, }; } exports.default = makeCryptoWith; //# sourceMappingURL=crypto.js.map