From ba692aaa20841e3d41d902578fb16d0f74d1ea05 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Tue, 22 Apr 2014 22:18:59 -0300 Subject: [PATCH] add new SecureRandom class that does the right thing Generating random numbers properly depends on the platform. The new getRandomBuffer method does the right thing on the right platform. It will sometimes fail due to insufficient entropy. The getPseudoRandomBuffer class is also provided that will never fail, but it is not cryptographically secure and should not be used for keys. --- bitcore.js | 1 + browser/build.js | 1 + lib/BIP32.js | 3 +- lib/Connection.js | 3 +- lib/SecureRandom.js | 5 ++++ lib/browser/SecureRandom.js | 23 +++++++++++++++ lib/common/SecureRandom.js | 28 ++++++++++++++++++ lib/node/SecureRandom.js | 10 +++++++ test/index.html | 1 + test/test.SecureRandom.js | 57 +++++++++++++++++++++++++++++++++++++ util/util.js | 31 -------------------- 11 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 lib/SecureRandom.js create mode 100644 lib/browser/SecureRandom.js create mode 100644 lib/common/SecureRandom.js create mode 100644 lib/node/SecureRandom.js create mode 100644 test/test.SecureRandom.js diff --git a/bitcore.js b/bitcore.js index aa0de4b..13f7293 100644 --- a/bitcore.js +++ b/bitcore.js @@ -20,6 +20,7 @@ requireWhenAccessed('const', './const'); requireWhenAccessed('Deserialize', './lib/Deserialize'); requireWhenAccessed('log', './util/log'); requireWhenAccessed('networks', './networks'); +requireWhenAccessed('SecureRandom', './lib/SecureRandom'); requireWhenAccessed('util', './util/util'); requireWhenAccessed('EncodedData', './util/EncodedData'); requireWhenAccessed('VersionedData', './util/VersionedData'); diff --git a/browser/build.js b/browser/build.js index 05f7a62..38f97e4 100644 --- a/browser/build.js +++ b/browser/build.js @@ -42,6 +42,7 @@ var modules = [ 'lib/SINKey', 'lib/Script', 'lib/ScriptInterpreter', + 'lib/SecureRandom', 'lib/Sign', 'lib/Transaction', 'lib/TransactionBuilder', diff --git a/lib/BIP32.js b/lib/BIP32.js index 94a7602..e1f8bfd 100644 --- a/lib/BIP32.js +++ b/lib/BIP32.js @@ -3,6 +3,7 @@ var base58 = imports.base58 || require('base58-native').base58; var coinUtil = imports.coinUtil || require('../util'); var Key = imports.Key || require('./Key'); var Point = imports.Point || require('./Point'); +var SecureRandom = imports.SecureRandom || require('./SecureRandom'); var bignum = imports.bignum || require('bignum'); var crypto = require('crypto'); var networks = require('../networks'); @@ -27,7 +28,7 @@ var BIP32 = function(bytes) { this.depth = 0x00; this.parentFingerprint = new Buffer([0, 0, 0, 0]); this.childIndex = new Buffer([0, 0, 0, 0]); - this.chainCode = Key.generateSync().private; + this.chainCode = SecureRandom.getRandomBuffer(32); this.eckey = Key.generateSync(); this.hasPrivateKey = true; this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); diff --git a/lib/Connection.js b/lib/Connection.js index f03e953..4b40d16 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -17,7 +17,8 @@ var util = imports.util || require('../util'); var Parser = imports.Parser || require('../util/BinaryParser'); var buffertools = imports.buffertools || require('buffertools'); var doubleSha256 = imports.doubleSha256 || util.twoSha256; -var nonce = util.generateNonce(); +var SecureRandom = imports.SecureRandom || require('./SecureRandom'); +var nonce = function() {return SecureRandom.getPseudoRandomBuffer(8)}; var BIP0031_VERSION = 60000; diff --git a/lib/SecureRandom.js b/lib/SecureRandom.js new file mode 100644 index 0000000..fa7f5ef --- /dev/null +++ b/lib/SecureRandom.js @@ -0,0 +1,5 @@ +if (process.versions) { + module.exports = require('./node/SecureRandom'); + return; +} +module.exports = require('./browser/SecureRandom'); diff --git a/lib/browser/SecureRandom.js b/lib/browser/SecureRandom.js new file mode 100644 index 0000000..79f1db5 --- /dev/null +++ b/lib/browser/SecureRandom.js @@ -0,0 +1,23 @@ +var imports = require('soop'); + +var SecureRandom = require('../common/SecureRandom'); + +SecureRandom.getRandomBuffer = function(size) { + if (!window.crypto && !window.msCrypto) + throw new Error('window.crypto not available'); + + if (window.crypto && window.crypto.getRandomValues) + var crypto = window.crypto; + else if (window.msCrypto && window.msCrypto.getRandomValues) //internet explorer + var crypto = window.msCrypto; + else + throw new Error('window.crypto.getRandomValues not available'); + + var bbuf = new Uint8Array(size); + crypto.getRandomValues(bbuf); + var buf = new Buffer(bbuf); + + return buf; +}; + +module.exports = require('soop')(SecureRandom); diff --git a/lib/common/SecureRandom.js b/lib/common/SecureRandom.js new file mode 100644 index 0000000..a276d22 --- /dev/null +++ b/lib/common/SecureRandom.js @@ -0,0 +1,28 @@ +var imports = require('soop'); + +var SecureRandom = function() { +}; + +/* secure random bytes that sometimes throws an error due to lack of entropy */ +SecureRandom.getRandomBuffer = function() {}; + +/* insecure random bytes, but it never fails */ +SecureRandom.getPseudoRandomBuffer = function(size) { + var b32 = 0x100000000; + var b = new Buffer(size); + + for (var i = 0; i <= size; i++) { + var j = Math.floor(i / 4); + var k = i - j * 4; + if (k == 0) { + r = Math.random() * b32; + b[i] = r & 0xff; + } else { + b[i] = (r = r >>> 8) & 0xff; + } + } + + return b; +}; + +module.exports = require('soop')(SecureRandom); diff --git a/lib/node/SecureRandom.js b/lib/node/SecureRandom.js new file mode 100644 index 0000000..3639f75 --- /dev/null +++ b/lib/node/SecureRandom.js @@ -0,0 +1,10 @@ +var imports = require('soop'); +var crypto = imports.crypto || require('crypto'); + +var SecureRandom = require('../common/SecureRandom'); + +SecureRandom.getRandomBuffer = function(size) { + return crypto.randomBytes(size); +} + +module.exports = require('soop')(SecureRandom); diff --git a/test/index.html b/test/index.html index 551c46d..9b8da7c 100644 --- a/test/index.html +++ b/test/index.html @@ -35,6 +35,7 @@ + diff --git a/test/test.SecureRandom.js b/test/test.SecureRandom.js new file mode 100644 index 0000000..ab8207b --- /dev/null +++ b/test/test.SecureRandom.js @@ -0,0 +1,57 @@ +'use strict'; + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); +var should = chai.should(); +var assert = chai.assert; +var SecureRandom = bitcore.SecureRandom; + +describe('SecureRandom', function() { + + describe('getRandomBuffer', function() { + + it('should return a buffer', function() { + var bytes = SecureRandom.getRandomBuffer(8); + bytes.length.should.equal(8); + Buffer.isBuffer(bytes).should.equal(true); + }); + + it('should not equate two 256 bit random buffers', function() { + var bytes1 = SecureRandom.getRandomBuffer(32); + var bytes2 = SecureRandom.getRandomBuffer(32); + bytes1.toString('hex').should.not.equal(bytes2.toString('hex')); + }); + + }); + + describe('getPseudoRandomBuffer', function() { + + it('should generate 7 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(7); + buf.length.should.equal(7); + }); + + it('should generate 8 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(8); + buf.length.should.equal(8); + }); + + it('should generate 9 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(9); + buf.length.should.equal(9); + }); + + it('should generate 90 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(90); + buf.length.should.equal(90); + }); + + it('should generate two 8 byte buffers that are not equal', function() { + var buf1 = SecureRandom.getPseudoRandomBuffer(8); + var buf2 = SecureRandom.getPseudoRandomBuffer(8); + buf1.toString('hex').should.not.equal(buf2.toString('hex')); + }); + + }); + +}); diff --git a/util/util.js b/util/util.js index f89dce8..a59df4b 100644 --- a/util/util.js +++ b/util/util.js @@ -10,7 +10,6 @@ if (inBrowser) { browser = require('../browser/vendor-bundle.js'); } - var sha256 = exports.sha256 = function(data) { return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); }; @@ -336,36 +335,6 @@ var createSynchrotron = exports.createSynchrotron = function(fn) { }; }; -/** - * Generate a random 64-bit number. - * - * With ideas from node-uuid: - * Copyright (c) 2010 Robert Kieffer - * https://github.com/broofa/node-uuid/ - * - * @returns Buffer random nonce - */ -var generateNonce = exports.generateNonce = function() { - var b32 = 0x100000000, - ff = 0xff; - var b = new Buffer(8), - i = 0; - - // Generate eight random bytes - r = Math.random() * b32; - b[i++] = r & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - r = Math.random() * b32; - b[i++] = r & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - - return b; -}; - /** * Decode difficulty bits. *