You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
12 KiB
419 lines
12 KiB
/** |
|
* 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. |
|
*/ |
|
var forge = require('./forge'); |
|
require('./util'); |
|
|
|
var _crypto = null; |
|
if(forge.util.isNodejs && !forge.options.usePureJavaScript && |
|
!process.versions['node-webkit']) { |
|
_crypto = require('crypto'); |
|
} |
|
|
|
/* PRNG API */ |
|
var prng = module.exports = 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, |
|
// no initial key bytes |
|
keyBytes: '' |
|
}; |
|
|
|
// 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(); |
|
|
|
// paranoid deviation from Fortuna: |
|
// reset key for every request to protect previously |
|
// generated random bytes should the key be discovered; |
|
// there is no 100ms based reseeding because of this |
|
// forced reseed for every `generate` call |
|
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; |
|
|
|
// paranoid deviation from Fortuna: |
|
// reset key for every request to protect previously |
|
// generated random bytes should the key be discovered; |
|
// there is no 100ms based reseeding because of this |
|
// forced reseed for every `generateSync` call |
|
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() { |
|
// update reseed count |
|
ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1; |
|
|
|
// goal is to update `key` via: |
|
// key = hash(key + s) |
|
// where 's' is all collected entropy from selected pools, then... |
|
|
|
// create a plugin-based message digest |
|
var md = ctx.plugin.md.create(); |
|
|
|
// consume current key bytes |
|
md.update(ctx.keyBytes); |
|
|
|
// digest the entropy of pools whose index k meet the |
|
// condition 'n mod 2^k == 0' where n is the number of reseeds |
|
var _2powK = 1; |
|
for(var k = 0; k < 32; ++k) { |
|
if(ctx.reseeds % _2powK === 0) { |
|
md.update(ctx.pools[k].digest().getBytes()); |
|
ctx.pools[k].start(); |
|
} |
|
_2powK = _2powK << 1; |
|
} |
|
|
|
// get digest for key bytes |
|
ctx.keyBytes = md.digest().getBytes(); |
|
|
|
// paranoid deviation from Fortuna: |
|
// update `seed` via `seed = hash(key)` |
|
// instead of initializing to zero once and only |
|
// ever incrementing it |
|
md.start(); |
|
md.update(ctx.keyBytes); |
|
var seedBytes = md.digest().getBytes(); |
|
|
|
// update state |
|
ctx.key = ctx.plugin.formatKey(ctx.keyBytes); |
|
ctx.seed = ctx.plugin.formatSeed(seedBytes); |
|
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; |
|
var globalScope = forge.util.globalScope; |
|
var _crypto = globalScope.crypto || globalScope.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(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; |
|
};
|
|
|