/*! * crypto.js - crypto for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var assert = require('assert'); var nativeCrypto, supersha, hash, aes; var isBrowser = (typeof process !== 'undefined' && process.browser) || typeof window !== 'undefined'; if (!isBrowser) { nativeCrypto = require('cry' + 'pto'); try { supersha = require('super' + 'sha'); } catch (e) { ; } } else { hash = require('hash.js'); aes = require('./aes'); } /** * @exports crypto */ var crypto = exports; /** * Hash with chosen algorithm. * @param {String} alg * @param {Buffer} data * @returns {Buffer} */ crypto.hash = function _hash(alg, data) { if (!nativeCrypto) return new Buffer(hash[alg]().update(data).digest()); return nativeCrypto.createHash(alg).update(data).digest(); }; /** * Hash with ripemd160. * @param {Buffer} data * @returns {Buffer} */ crypto.ripemd160 = function ripemd160(data) { return crypto.hash('ripemd160', data); }; /** * Hash with sha1. * @param {Buffer} data * @returns {Buffer} */ crypto.sha1 = function sha1(data) { return crypto.hash('sha1', data); }; /** * Hash with sha256. * @param {Buffer} data * @returns {Buffer} */ crypto.sha256 = function sha256(data) { if (supersha) return supersha.sha256(data); return crypto.hash('sha256', data); }; /** * Hash with sha256 and ripemd160 (OP_HASH160). * @param {Buffer} data * @returns {Buffer} */ crypto.hash160 = function hash160(data) { return crypto.ripemd160(crypto.sha256(data)); }; /** * Hash with sha256 twice (OP_HASH256). * @param {Buffer} data * @returns {Buffer} */ crypto.hash256 = function hash256(data) { if (supersha) return supersha.hash256(data); return crypto.sha256(crypto.sha256(data)); }; /** * Create a sha256 checksum (common in bitcoin). * @param {Buffer} data * @returns {Buffer} */ crypto.checksum = function checksum(data) { return crypto.hash256(data).slice(0, 4); }; /** * Create an HMAC. * @param {String} alg * @param {Buffer} data * @param {Buffer} salt * @returns {Buffer} HMAC */ crypto.hmac = function hmac(alg, data, salt) { var hmac; if (!nativeCrypto) { hmac = hash.hmac(hash[alg], salt); return new Buffer(hmac.update(data).digest()); } hmac = nativeCrypto.createHmac(alg, salt); return hmac.update(data).digest(); }; /** * Perform key stretching using PBKDF2. * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter * @param {Number} len * @param {String} alg * @returns {Buffer} */ crypto.pbkdf2Sync = function pbkdf2Sync(key, salt, iter, len, alg) { if (typeof key === 'string') key = new Buffer(key, 'utf8'); if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); if (nativeCrypto && nativeCrypto.pbkdf2Sync) return nativeCrypto.pbkdf2Sync(key, salt, iter, len, alg); return crypto._pbkdf2(key, salt, iter, len, alg); }; /** * Execute pbkdf2 asynchronously. * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter * @param {Number} len * @param {String} alg * @param {Function} callback */ crypto.pbkdf2 = function pbkdf2(key, salt, iter, len, alg, callback) { var result; if (typeof key === 'string') key = new Buffer(key, 'utf8'); if (typeof salt === 'string') salt = new Buffer(salt, 'utf8'); if (nativeCrypto && nativeCrypto.pbkdf2) return nativeCrypto.pbkdf2(key, salt, iter, len, alg, callback); try { result = crypto._pbkdf2(key, salt, iter, len, alg); } catch (e) { return callback(e); } return callback(null, result); }; /** * Derive a key using pbkdf2 with 50,000 iterations. * @param {Buffer|String} passphrase * @param {Function} callback */ crypto.derive = function derive(passphrase, callback) { crypto.pbkdf2(passphrase, 'bcoin', 50000, 32, 'sha256', callback); }; /** * Encrypt with aes-256-cbc. Derives key with {@link crypto.derive}. * @param {Buffer} data * @param {Buffer|String} passphrase * @param {Buffer} iv - 128 bit initialization vector. * @param {Function} callback */ crypto.encrypt = function encrypt(data, passphrase, iv, callback) { assert(Buffer.isBuffer(data)); assert(passphrase, 'No passphrase.'); assert(Buffer.isBuffer(iv)); crypto.derive(passphrase, function(err, key) { if (err) return callback(err); try { data = crypto.encipher(data, key, iv); } catch (e) { key.fill(0); return callback(e); } key.fill(0); return callback(null, data); }); }; /** * Encrypt with aes-256-cbc. * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Buffer} */ crypto.encipher = function encipher(data, key, iv) { var cipher; if (!nativeCrypto) return aes.cbc.encrypt(data, key, iv); cipher = nativeCrypto.createCipheriv('aes-256-cbc', key, iv); return Buffer.concat([ cipher.update(data), cipher.final() ]); }; /** * Decrypt with aes-256-cbc. Derives key with {@link crypto.derive}. * @param {Buffer} data * @param {Buffer|String} passphrase * @param {Buffer} iv - 128 bit initialization vector. * @param {Function} callback */ crypto.decrypt = function decrypt(data, passphrase, iv, callback) { assert(Buffer.isBuffer(data)); assert(passphrase, 'No passphrase.'); assert(Buffer.isBuffer(iv)); crypto.derive(passphrase, function(err, key) { if (err) return callback(err); try { data = crypto.decipher(data, key, iv); } catch (e) { key.fill(0); return callback(e); } key.fill(0); return callback(null, data); }); }; /** * Decrypt with aes-256-cbc. * @param {Buffer} data * @param {Buffer} key - 256 bit key. * @param {Buffer} iv - 128 bit initialization vector. * @returns {Buffer} */ crypto.decipher = function decipher(data, key, iv) { var decipher; if (!nativeCrypto) return aes.cbc.decrypt(data, key, iv); decipher = nativeCrypto.createDecipheriv('aes-256-cbc', key, iv); return Buffer.concat([ decipher.update(data), decipher.final() ]); }; /** * Perform key stretching using PBKDF2. * @private * @param {Buffer} key * @param {Buffer} salt * @param {Number} iter * @param {Number} len * @param {String} alg * @returns {Buffer} */ crypto._pbkdf2 = function pbkdf2(key, salt, iter, len, alg) { var size = crypto.hash(alg, new Buffer(0)).length; var blocks = Math.ceil(len / size); var out = new Buffer(len); var buf = new Buffer(salt.length + 4); var block = new Buffer(size); var pos = 0; var i, j, k, mac; salt.copy(buf, 0); for (i = 0; i < blocks; i++) { buf.writeUInt32BE(i + 1, salt.length, true); mac = crypto.hmac(alg, buf, key); mac.copy(block, 0); for (j = 1; j < iter; j++) { mac = crypto.hmac(alg, mac, key); for (k = 0; k < size; k++) block[k] ^= mac[k]; } block.copy(out, pos); pos += size; } return out; }; /** * Perform hkdf extraction. * @param {Buffer} ikm * @param {Buffer} salt * @param {String} alg * @returns {Buffer} */ crypto.hkdfExtract = function hkdfExtract(ikm, salt, alg) { return crypto.hmac(alg, ikm, salt); }; /** * Perform hkdf expansion. * @param {Buffer} prk * @param {Buffer} info * @param {Number} len * @param {String} alg * @returns {Buffer} */ crypto.hkdfExpand = function hkdfExpand(prk, info, len, alg) { var size = crypto.hash(alg, new Buffer(0)).length; var blocks = Math.ceil(len / size); var i, okm, buf, out; if (blocks > 255) throw new Error('Too many blocks.'); okm = new Buffer(len); if (blocks === 0) return okm; buf = new Buffer(size + info.length + 1); // First round: info.copy(buf, size); buf[buf.length - 1] = 1; out = crypto.hmac(alg, buf.slice(size), prk); out.copy(okm, 0); for (i = 1; i < blocks; i++) { out.copy(buf, 0); buf[buf.length - 1]++; out = crypto.hmac(alg, buf, prk); out.copy(okm, i * size); } return okm; }; /** * memcmp in constant time (can only return true or false). * This protects us against timing attacks when * comparing an input against a secret string. * @see https://cryptocoding.net/index.php/Coding_rules * @see `$ man 3 memcmp` (NetBSD's consttime_memequal) * @param {Buffer} a * @param {Buffer} b * @returns {Boolean} */ crypto.ccmp = function ccmp(a, b) { var i, res; if (!Buffer.isBuffer(a)) return false; if (!Buffer.isBuffer(b)) return false; if (b.length === 0) return a.length === 0; res = a.length ^ b.length; for (i = 0; i < a.length; i++) res |= a[i] ^ b[i % b.length]; return res === 0; }; /** * Build a merkle tree from leaves. * @param {Buffer[]} leaves * @returns {Buffer[]} Tree (in rare cases this may return null). */ crypto.buildMerkleTree = function buildMerkleTree(leaves) { var tree = leaves.slice(); var size = leaves.length; var i, j, i2, hash, left, right, buf; if (size > 1) buf = new Buffer(64); for (j = 0; size > 1; size = ((size + 1) / 2) | 0) { for (i = 0; i < size; i += 2) { i2 = Math.min(i + 1, size - 1); left = tree[j + i]; right = tree[j + i2]; if (i2 === i + 1 && i2 + 1 === size && left.compare(right) === 0) { return; } left.copy(buf, 0); right.copy(buf, 32); hash = crypto.hash256(buf); tree.push(hash); } j += size; } if (tree.length === 0) return; return tree; }; /** * Calculate merkle root from leaves. * @param {Buffer[]} leaves * @returns {Buffer?} Merkle root. */ crypto.getMerkleRoot = function getMerkleRoot(leaves) { var tree = crypto.buildMerkleTree(leaves); if (!tree) return; return tree[tree.length - 1]; }; /** * Collect a merkle branch at vector index. * @param {Number} index * @param {Buffer[]} leaves * @returns {Buffer[]} branch */ crypto.getMerkleBranch = function getMerkleBranch(index, leaves) { var tree = crypto.buildMerkleTree(leaves); var size = leaves.length; var branch = []; var j = 0; var i; for (; size > 1; size = (size + 1) / 2 | 0) { i = Math.min(index ^ 1, size - 1); branch.push(tree[j + i]); index >>>= 1; j += size; } return branch; }; /** * Check a merkle branch at vector index. * @param {Buffer} hash * @param {Buffer[]} branch * @param {Number} index * @returns {Buffer} Hash. */ crypto.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) { var i, otherside, buf; if (branch.length === 0) return hash; buf = new Buffer(64); for (i = 0; i < branch.length; i++) { otherside = branch[i]; if (index & 1) { otherside.copy(buf, 0); hash.copy(buf, 32); } else { hash.copy(buf, 0); otherside.copy(buf, 32); } hash = crypto.hash256(buf); index >>>= 1; } return hash; };