3 let Crypto = require("crypto");
5 let Cipher = module.exports;
7 const ALG = "aes-128-cbc";
11 * @param {String} passphrase - what the human entered
12 * @param {String} shadow - encrypted, hashed, key-expanded passphrase
14 Cipher.checkPassphrase = async function (passphrase, shadow) {
15 let key128 = await Cipher.deriveKey(passphrase);
16 let cipher = Cipher.create(key128);
20 plainShadow = cipher.decrypt(shadow);
24 if (!msg.includes("decrypt")) {
30 let untrustedShadow = Crypto.createHash("sha512")
33 return Cipher.secureCompare(plainShadow, untrustedShadow);
37 * @param {String} passphrase - what the human entered
39 Cipher.shadowPassphrase = async function (passphrase) {
40 let key128 = await Cipher.deriveKey(passphrase);
41 let plainShadow = Crypto.createHash("sha512").update(key128).digest("base64");
42 let cipher = Cipher.create(key128);
43 let shadow = cipher.encrypt(plainShadow);
49 * @param {String} passphrase
51 Cipher.deriveKey = async function (passphrase) {
52 // See https://crypto.stackexchange.com/a/6557
53 // and https://nodejs.org/api/crypto.html#cryptohkdfdigest-ikm-salt-info-keylen-callback
54 const DIGEST = "sha512";
55 const SALT = Buffer.from("crowdnode-cli", "utf8");
56 // 'info' is a string describing a sub-context
57 const INFO = Buffer.from("staking-keys", "utf8");
60 let ikm = Buffer.from(passphrase, "utf8");
61 let key128 = await new Promise(function (resolve, reject) {
63 Crypto.hkdf(DIGEST, ikm, SALT, INFO, SIZE, function (err, key128) {
68 resolve(Buffer.from(key128));
76 * @param {String} shadow
77 * @param {Buffer} key128
79 Cipher.checkShadow = function (shadow, key128) {
80 let untrustedShadow = Crypto.createHash("sha512")
83 return Cipher.secureCompare(shadow, untrustedShadow);
90 Cipher.secureCompare = function (a, b) {
92 throw new Error("[secure compare] reference string should not be empty");
95 if (a.length !== b.length) {
99 return Crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
103 * @param {Buffer} key128
105 Cipher.create = function (key128) {
106 //let sharedSecret = Buffer.from(key128, "base64");
111 * @param {String} plaintext
113 cipher.encrypt = function (plaintext) {
114 let initializationVector = Crypto.randomBytes(IV_SIZE); // IV is always 16-bytes
117 let _cipher = Crypto.createCipheriv(ALG, key128, initializationVector);
118 encrypted += _cipher.update(plaintext, "utf8", "base64");
119 encrypted += _cipher.final("base64");
122 toWeb64(initializationVector.toString("base64")) +
127 toWeb64(initializationVector.toString("base64"))
132 * @param {String} parts
134 cipher.decrypt = function (parts) {
135 let [initializationVector, encrypted, initializationVectorBak] =
138 if (initializationVector !== initializationVectorBak) {
139 console.error("corrupt (but possibly recoverable) initialization vector");
142 let iv = Buffer.from(initializationVector, "base64");
143 let _cipher = Crypto.createDecipheriv(ALG, key128, iv);
144 plaintext += _cipher.update(encrypted, "base64", "utf8");
145 plaintext += _cipher.final("utf8");
156 function toWeb64(x) {
157 return x.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");