UNPKG

crowdnode

Version:

Manage your stake in Đash with the CrowdNode Blockchain API

159 lines (133 loc) 3.94 kB
"use strict"; let Crypto = require("crypto"); let Cipher = module.exports; const ALG = "aes-128-cbc"; const IV_SIZE = 16; /** * @param {String} passphrase - what the human entered * @param {String} shadow - encrypted, hashed, key-expanded passphrase */ Cipher.checkPassphrase = async function (passphrase, shadow) { let key128 = await Cipher.deriveKey(passphrase); let cipher = Cipher.create(key128); let plainShadow; try { plainShadow = cipher.decrypt(shadow); } catch (e) { //@ts-ignore let msg = e.message; if (!msg.includes("decrypt")) { throw e; } return false; } let untrustedShadow = Crypto.createHash("sha512") .update(key128) .digest("base64"); return Cipher.secureCompare(plainShadow, untrustedShadow); }; /** * @param {String} passphrase - what the human entered */ Cipher.shadowPassphrase = async function (passphrase) { let key128 = await Cipher.deriveKey(passphrase); let plainShadow = Crypto.createHash("sha512").update(key128).digest("base64"); let cipher = Cipher.create(key128); let shadow = cipher.encrypt(plainShadow); return shadow; }; /** * @param {String} passphrase */ Cipher.deriveKey = async function (passphrase) { // See https://crypto.stackexchange.com/a/6557 // and https://nodejs.org/api/crypto.html#cryptohkdfdigest-ikm-salt-info-keylen-callback const DIGEST = "sha512"; const SALT = Buffer.from("crowdnode-cli", "utf8"); // 'info' is a string describing a sub-context const INFO = Buffer.from("staking-keys", "utf8"); const SIZE = 16; let ikm = Buffer.from(passphrase, "utf8"); let key128 = await new Promise(function (resolve, reject) { //@ts-ignore Crypto.hkdf(DIGEST, ikm, SALT, INFO, SIZE, function (err, key128) { if (err) { reject(err); return; } resolve(Buffer.from(key128)); }); }); return key128; }; /** * @param {String} shadow * @param {Buffer} key128 */ Cipher.checkShadow = function (shadow, key128) { let untrustedShadow = Crypto.createHash("sha512") .update(key128) .digest("base64"); return Cipher.secureCompare(shadow, untrustedShadow); }; /** * @param {String} a * @param {String} b */ Cipher.secureCompare = function (a, b) { if (!a && !b) { throw new Error("[secure compare] reference string should not be empty"); } if (a.length !== b.length) { return false; } return Crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); }; /** * @param {Buffer} key128 */ Cipher.create = function (key128) { //let sharedSecret = Buffer.from(key128, "base64"); let cipher = {}; /** * @param {String} plaintext */ cipher.encrypt = function (plaintext) { let initializationVector = Crypto.randomBytes(IV_SIZE); // IV is always 16-bytes let encrypted = ""; let _cipher = Crypto.createCipheriv(ALG, key128, initializationVector); encrypted += _cipher.update(plaintext, "utf8", "base64"); encrypted += _cipher.final("base64"); return ( toWeb64(initializationVector.toString("base64")) + ":" + toWeb64(encrypted) + ":" + // as a backup toWeb64(initializationVector.toString("base64")) ); }; /** * @param {String} parts */ cipher.decrypt = function (parts) { let [initializationVector, encrypted, initializationVectorBak] = parts.split(":"); let plaintext = ""; if (initializationVector !== initializationVectorBak) { console.error("corrupt (but possibly recoverable) initialization vector"); } let iv = Buffer.from(initializationVector, "base64"); let _cipher = Crypto.createDecipheriv(ALG, key128, iv); plaintext += _cipher.update(encrypted, "base64", "utf8"); plaintext += _cipher.final("utf8"); return plaintext; }; return cipher; }; /** * @param {String} x */ function toWeb64(x) { return x.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); }