/** * A javascript implementation of a cryptographically-secure * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed * here though the use of SHA-256 is not enforced; when generating an * a PRNG context, the hashing algorithm and block cipher used for * the generator are specified via a plugin. * * @author Dave Longley * * Copyright (c) 2010-2014 Digital Bazaar, Inc. */ (function() { /* ########## Begin module implementation ########## */ function initModule(forge) { var _nodejs = ( typeof process !== 'undefined' && process.versions && process.versions.node); var _crypto = null; if(!forge.disableNativeCode && _nodejs && !process.versions['node-webkit']) { _crypto = require('crypto'); } /* PRNG API */ var prng = forge.prng = forge.prng || {}; /** * Creates a new PRNG context. * * A PRNG plugin must be passed in that will provide: * * 1. A function that initializes the key and seed of a PRNG context. It * will be given a 16 byte key and a 16 byte seed. Any key expansion * or transformation of the seed from a byte string into an array of * integers (or similar) should be performed. * 2. The cryptographic function used by the generator. It takes a key and * a seed. * 3. A seed increment function. It takes the seed and returns seed + 1. * 4. An api to create a message digest. * * For an example, see random.js. * * @param plugin the PRNG plugin to use. */ prng.create = function(plugin) { var ctx = { plugin: plugin, key: null, seed: null, time: null, // number of reseeds so far reseeds: 0, // amount of data generated so far generated: 0 }; // create 32 entropy pools (each is a message digest) var md = plugin.md; var pools = new Array(32); for(var i = 0; i < 32; ++i) { pools[i] = md.create(); } ctx.pools = pools; // entropy pools are written to cyclically, starting at index 0 ctx.pool = 0; /** * Generates random bytes. The bytes may be generated synchronously or * asynchronously. Web workers must use the asynchronous interface or * else the behavior is undefined. * * @param count the number of random bytes to generate. * @param [callback(err, bytes)] called once the operation completes. * * @return count random bytes as a string. */ ctx.generate = function(count, callback) { // do synchronously if(!callback) { return ctx.generateSync(count); } // simple generator using counter-based CBC var cipher = ctx.plugin.cipher; var increment = ctx.plugin.increment; var formatKey = ctx.plugin.formatKey; var formatSeed = ctx.plugin.formatSeed; var b = forge.util.createBuffer(); // reset key for every request ctx.key = null; generate(); function generate(err) { if(err) { return callback(err); } // sufficient bytes generated if(b.length() >= count) { return callback(null, b.getBytes(count)); } // if amount of data generated is greater than 1 MiB, trigger reseed if(ctx.generated > 0xfffff) { ctx.key = null; } if(ctx.key === null) { // prevent stack overflow return forge.util.nextTick(function() { _reseed(generate); }); } // generate the random bytes var bytes = cipher(ctx.key, ctx.seed); ctx.generated += bytes.length; b.putBytes(bytes); // generate bytes for a new key and seed ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed))); ctx.seed = formatSeed(cipher(ctx.key, ctx.seed)); forge.util.setImmediate(generate); } }; /** * Generates random bytes synchronously. * * @param count the number of random bytes to generate. * * @return count random bytes as a string. */ ctx.generateSync = function(count) { // simple generator using counter-based CBC var cipher = ctx.plugin.cipher; var increment = ctx.plugin.increment; var formatKey = ctx.plugin.formatKey; var formatSeed = ctx.plugin.formatSeed; // reset key for every request ctx.key = null; var b = forge.util.createBuffer(); while(b.length() < count) { // if amount of data generated is greater than 1 MiB, trigger reseed if(ctx.generated > 0xfffff) { ctx.key = null; } if(ctx.key === null) { _reseedSync(); } // generate the random bytes var bytes = cipher(ctx.key, ctx.seed); ctx.generated += bytes.length; b.putBytes(bytes); // generate bytes for a new key and seed ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed))); ctx.seed = formatSeed(cipher(ctx.key, ctx.seed)); } return b.getBytes(count); }; /** * Private function that asynchronously reseeds a generator. * * @param callback(err) called once the operation completes. */ function _reseed(callback) { if(ctx.pools[0].messageLength >= 32) { _seed(); return callback(); } // not enough seed data... var needed = (32 - ctx.pools[0].messageLength) << 5; ctx.seedFile(needed, function(err, bytes) { if(err) { return callback(err); } ctx.collect(bytes); _seed(); callback(); }); } /** * Private function that synchronously reseeds a generator. */ function _reseedSync() { if(ctx.pools[0].messageLength >= 32) { return _seed(); } // not enough seed data... var needed = (32 - ctx.pools[0].messageLength) << 5; ctx.collect(ctx.seedFileSync(needed)); _seed(); } /** * Private function that seeds a generator once enough bytes are available. */ function _seed() { // create a plugin-based message digest var md = ctx.plugin.md.create(); // digest pool 0's entropy and restart it md.update(ctx.pools[0].digest().getBytes()); ctx.pools[0].start(); // digest the entropy of other pools whose index k meet the // condition '2^k mod n == 0' where n is the number of reseeds var k = 1; for(var i = 1; i < 32; ++i) { // prevent signed numbers from being used k = (k === 31) ? 0x80000000 : (k << 2); if(k % ctx.reseeds === 0) { md.update(ctx.pools[i].digest().getBytes()); ctx.pools[i].start(); } } // get digest for key bytes and iterate again for seed bytes var keyBytes = md.digest().getBytes(); md.start(); md.update(keyBytes); var seedBytes = md.digest().getBytes(); // update ctx.key = ctx.plugin.formatKey(keyBytes); ctx.seed = ctx.plugin.formatSeed(seedBytes); ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1; ctx.generated = 0; } /** * The built-in default seedFile. This seedFile is used when entropy * is needed immediately. * * @param needed the number of bytes that are needed. * * @return the random bytes. */ function defaultSeedFile(needed) { // use window.crypto.getRandomValues strong source of entropy if available var getRandomValues = null; if(typeof window !== 'undefined') { var _crypto = window.crypto || window.msCrypto; if(_crypto && _crypto.getRandomValues) { getRandomValues = function(arr) { return _crypto.getRandomValues(arr); }; } } var b = forge.util.createBuffer(); if(getRandomValues) { while(b.length() < needed) { // max byte length is 65536 before QuotaExceededError is thrown // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4); var entropy = new Uint32Array(Math.floor(count)); try { getRandomValues(entropy); for(var i = 0; i < entropy.length; ++i) { b.putInt32(entropy[i]); } } catch(e) { /* only ignore QuotaExceededError */ if(!(typeof QuotaExceededError !== 'undefined' && e instanceof QuotaExceededError)) { throw e; } } } } // be sad and add some weak random data if(b.length() < needed) { /* Draws from Park-Miller "minimal standard" 31 bit PRNG, implemented with David G. Carta's optimization: with 32 bit math and without division (Public Domain). */ var hi, lo, next; var seed = Math.floor(Math.random() * 0x010000); while(b.length() < needed) { lo = 16807 * (seed & 0xFFFF); hi = 16807 * (seed >> 16); lo += (hi & 0x7FFF) << 16; lo += hi >> 15; lo = (lo & 0x7FFFFFFF) + (lo >> 31); seed = lo & 0xFFFFFFFF; // consume lower 3 bytes of seed for(var i = 0; i < 3; ++i) { // throw in more pseudo random next = seed >>> (i << 3); next ^= Math.floor(Math.random() * 0x0100); b.putByte(String.fromCharCode(next & 0xFF)); } } } return b.getBytes(needed); } // initialize seed file APIs if(_crypto) { // use nodejs async API ctx.seedFile = function(needed, callback) { _crypto.randomBytes(needed, function(err, bytes) { if(err) { return callback(err); } callback(null, bytes.toString()); }); }; // use nodejs sync API ctx.seedFileSync = function(needed) { return _crypto.randomBytes(needed).toString(); }; } else { ctx.seedFile = function(needed, callback) { try { callback(null, defaultSeedFile(needed)); } catch(e) { callback(e); } }; ctx.seedFileSync = defaultSeedFile; } /** * Adds entropy to a prng ctx's accumulator. * * @param bytes the bytes of entropy as a string. */ ctx.collect = function(bytes) { // iterate over pools distributing entropy cyclically var count = bytes.length; for(var i = 0; i < count; ++i) { ctx.pools[ctx.pool].update(bytes.substr(i, 1)); ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1; } }; /** * Collects an integer of n bits. * * @param i the integer entropy. * @param n the number of bits in the integer. */ ctx.collectInt = function(i, n) { var bytes = ''; for(var x = 0; x < n; x += 8) { bytes += String.fromCharCode((i >> x) & 0xFF); } ctx.collect(bytes); }; /** * Registers a Web Worker to receive immediate entropy from the main thread. * This method is required until Web Workers can access the native crypto * API. This method should be called twice for each created worker, once in * the main thread, and once in the worker itself. * * @param worker the worker to register. */ ctx.registerWorker = function(worker) { // worker receives random bytes if(worker === self) { ctx.seedFile = function(needed, callback) { function listener(e) { var data = e.data; if(data.forge && data.forge.prng) { self.removeEventListener('message', listener); callback(data.forge.prng.err, data.forge.prng.bytes); } } self.addEventListener('message', listener); self.postMessage({forge: {prng: {needed: needed}}}); }; } else { // main thread sends random bytes upon request var listener = function(e) { var data = e.data; if(data.forge && data.forge.prng) { ctx.seedFile(data.forge.prng.needed, function(err, bytes) { worker.postMessage({forge: {prng: {err: err, bytes: bytes}}}); }); } }; // TODO: do we need to remove the event listener when the worker dies? worker.addEventListener('message', listener); } }; return ctx; }; } // end module implementation /* ########## Begin module wrapper ########## */ var name = 'prng'; if(typeof define !== 'function') { // NodeJS -> AMD if(typeof module === 'object' && module.exports) { var nodeJS = true; define = function(ids, factory) { factory(require, module); }; } else { //