blockstack
Version:
The Blockstack Javascript library for authentication, identity, and storage.
152 lines • 5.93 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const bip39_1 = require("bip39");
const cryptoRandom_1 = require("./cryptoRandom");
const sha2Hash_1 = require("./sha2Hash");
const hmacSha256_1 = require("./hmacSha256");
const aesCipher_1 = require("./aesCipher");
const pbkdf2_1 = require("./pbkdf2");
/**
* Encrypt a raw mnemonic phrase to be password protected
* @param {string} phrase - Raw mnemonic phrase
* @param {string} password - Password to encrypt mnemonic with
* @return {Promise<Buffer>} The encrypted phrase
* @private
* @ignore
* */
function encryptMnemonic(phrase, password, opts) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
// hex encoded mnemonic string
let mnemonicEntropy;
try {
// must be bip39 mnemonic
mnemonicEntropy = bip39_1.mnemonicToEntropy(phrase);
}
catch (error) {
console.error('Invalid mnemonic phrase provided');
console.error(error);
throw new Error('Not a valid bip39 mnemonic');
}
// normalize plaintext to fixed length byte string
const plaintextNormalized = Buffer.from(mnemonicEntropy, 'hex');
// AES-128-CBC with SHA256 HMAC
const pbkdf2 = yield pbkdf2_1.createPbkdf2();
let salt;
if (opts && opts.getRandomBytes) {
salt = opts.getRandomBytes(16);
}
else {
salt = cryptoRandom_1.randomBytes(16);
}
const keysAndIV = yield pbkdf2.derive(password, salt, 100000, 48, 'sha512');
const encKey = keysAndIV.slice(0, 16);
const macKey = keysAndIV.slice(16, 32);
const iv = keysAndIV.slice(32, 48);
const cipher = yield aesCipher_1.createCipher();
const cipherText = yield cipher.encrypt('aes-128-cbc', encKey, iv, plaintextNormalized);
const hmacPayload = Buffer.concat([salt, cipherText]);
const hmacSha256 = yield hmacSha256_1.createHmacSha256();
const hmacDigest = yield hmacSha256.digest(macKey, hmacPayload);
const payload = Buffer.concat([salt, hmacDigest, cipherText]);
return payload;
});
}
exports.encryptMnemonic = encryptMnemonic;
// Used to distinguish bad password during decrypt vs invalid format
class PasswordError extends Error {
}
/**
* @ignore
*/
function decryptMnemonicBuffer(dataBuffer, password) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const salt = dataBuffer.slice(0, 16);
const hmacSig = dataBuffer.slice(16, 48); // 32 bytes
const cipherText = dataBuffer.slice(48);
const hmacPayload = Buffer.concat([salt, cipherText]);
const pbkdf2 = yield pbkdf2_1.createPbkdf2();
const keysAndIV = yield pbkdf2.derive(password, salt, 100000, 48, 'sha512');
const encKey = keysAndIV.slice(0, 16);
const macKey = keysAndIV.slice(16, 32);
const iv = keysAndIV.slice(32, 48);
const decipher = yield aesCipher_1.createCipher();
const decryptedResult = yield decipher.decrypt('aes-128-cbc', encKey, iv, cipherText);
const hmacSha256 = yield hmacSha256_1.createHmacSha256();
const hmacDigest = yield hmacSha256.digest(macKey, hmacPayload);
// hash both hmacSig and hmacDigest so string comparison time
// is uncorrelated to the ciphertext
const sha2Hash = yield sha2Hash_1.createSha2Hash();
const hmacSigHash = yield sha2Hash.digest(hmacSig);
const hmacDigestHash = yield sha2Hash.digest(hmacDigest);
if (!hmacSigHash.equals(hmacDigestHash)) {
// not authentic
throw new PasswordError('Wrong password (HMAC mismatch)');
}
let mnemonic;
try {
mnemonic = bip39_1.entropyToMnemonic(decryptedResult);
}
catch (error) {
console.error('Error thrown by `entropyToMnemonic`');
console.error(error);
throw new PasswordError('Wrong password (invalid plaintext)');
}
if (!bip39_1.validateMnemonic(mnemonic)) {
throw new PasswordError('Wrong password (invalid plaintext)');
}
return mnemonic;
});
}
/**
* Decrypt legacy triplesec keys
* @param {Buffer} dataBuffer - The encrypted key
* @param {String} password - Password for data
* @return {Promise<Buffer>} Decrypted seed
* @private
* @ignore
*/
function decryptLegacy(dataBuffer, password, triplesecDecrypt) {
return new Promise((resolve, reject) => {
if (!triplesecDecrypt) {
reject(new Error('The `triplesec.decrypt` function must be provided'));
}
triplesecDecrypt({
key: Buffer.from(password),
data: dataBuffer
}, (err, plaintextBuffer) => {
if (!err) {
resolve(plaintextBuffer);
}
else {
reject(err);
}
});
});
}
/**
* Decrypt an encrypted mnemonic phrase with a password.
* Legacy triplesec encrypted payloads are also supported.
* @param data - Buffer or hex-encoded string of the encrypted mnemonic
* @param password - Password for data
* @return the raw mnemonic phrase
* @private
* @ignore
*/
function decryptMnemonic(data, password, triplesecDecrypt) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'hex');
try {
return yield decryptMnemonicBuffer(dataBuffer, password);
}
catch (err) {
if (err instanceof PasswordError) {
throw err;
}
const data = yield decryptLegacy(dataBuffer, password, triplesecDecrypt);
return data.toString();
}
});
}
exports.decryptMnemonic = decryptMnemonic;
//# sourceMappingURL=wallet.js.map