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.

192 lines
5.3 KiB

4 years ago
/**
* An API for getting cryptographically-secure random bytes. The bytes are
* generated using the Fortuna algorithm devised by Bruce Schneier and
* Niels Ferguson.
*
* Getting strong random bytes is not yet easy to do in javascript. The only
* truish random entropy that can be collected is from the mouse, keyboard, or
* from timing with respect to page loads, etc. This generator makes a poor
* attempt at providing random bytes when those sources haven't yet provided
* enough entropy to initially seed or to reseed the PRNG.
*
* @author Dave Longley
*
* Copyright (c) 2009-2014 Digital Bazaar, Inc.
*/
var forge = require('./forge');
require('./aes');
require('./sha256');
require('./prng');
require('./util');
(function() {
// forge.random already defined
if(forge.random && forge.random.getBytes) {
module.exports = forge.random;
return;
}
(function(jQuery) {
// the default prng plugin, uses AES-128
var prng_aes = {};
var _prng_aes_output = new Array(4);
var _prng_aes_buffer = forge.util.createBuffer();
prng_aes.formatKey = function(key) {
// convert the key into 32-bit integers
var tmp = forge.util.createBuffer(key);
key = new Array(4);
key[0] = tmp.getInt32();
key[1] = tmp.getInt32();
key[2] = tmp.getInt32();
key[3] = tmp.getInt32();
// return the expanded key
return forge.aes._expandKey(key, false);
};
prng_aes.formatSeed = function(seed) {
// convert seed into 32-bit integers
var tmp = forge.util.createBuffer(seed);
seed = new Array(4);
seed[0] = tmp.getInt32();
seed[1] = tmp.getInt32();
seed[2] = tmp.getInt32();
seed[3] = tmp.getInt32();
return seed;
};
prng_aes.cipher = function(key, seed) {
forge.aes._updateBlock(key, seed, _prng_aes_output, false);
_prng_aes_buffer.putInt32(_prng_aes_output[0]);
_prng_aes_buffer.putInt32(_prng_aes_output[1]);
_prng_aes_buffer.putInt32(_prng_aes_output[2]);
_prng_aes_buffer.putInt32(_prng_aes_output[3]);
return _prng_aes_buffer.getBytes();
};
prng_aes.increment = function(seed) {
// FIXME: do we care about carry or signed issues?
++seed[3];
return seed;
};
prng_aes.md = forge.md.sha256;
/**
* Creates a new PRNG.
*/
function spawnPrng() {
var ctx = forge.prng.create(prng_aes);
/**
* Gets random bytes. If a native secure crypto API is unavailable, this
* method tries to make the bytes more unpredictable by drawing from data that
* can be collected from the user of the browser, eg: mouse movement.
*
* If a callback is given, this method will be called asynchronously.
*
* @param count the number of random bytes to get.
* @param [callback(err, bytes)] called once the operation completes.
*
* @return the random bytes in a string.
*/
ctx.getBytes = function(count, callback) {
return ctx.generate(count, callback);
};
/**
* Gets random bytes asynchronously. If a native secure crypto API is
* unavailable, this method tries to make the bytes more unpredictable by
* drawing from data that can be collected from the user of the browser,
* eg: mouse movement.
*
* @param count the number of random bytes to get.
*
* @return the random bytes in a string.
*/
ctx.getBytesSync = function(count) {
return ctx.generate(count);
};
return ctx;
}
// create default prng context
var _ctx = spawnPrng();
// add other sources of entropy only if window.crypto.getRandomValues is not
// available -- otherwise this source will be automatically used by the prng
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);
};
}
if(forge.options.usePureJavaScript ||
(!forge.util.isNodejs && !getRandomValues)) {
// if this is a web worker, do not use weak entropy, instead register to
// receive strong entropy asynchronously from the main thread
if(typeof window === 'undefined' || window.document === undefined) {
// FIXME:
}
// get load time entropy
_ctx.collectInt(+new Date(), 32);
// add some entropy from navigator object
if(typeof(navigator) !== 'undefined') {
var _navBytes = '';
for(var key in navigator) {
try {
if(typeof(navigator[key]) == 'string') {
_navBytes += navigator[key];
}
} catch(e) {
/* Some navigator keys might not be accessible, e.g. the geolocation
attribute throws an exception if touched in Mozilla chrome://
context.
Silently ignore this and just don't use this as a source of
entropy. */
}
}
_ctx.collect(_navBytes);
_navBytes = null;
}
// add mouse and keyboard collectors if jquery is available
if(jQuery) {
// set up mouse entropy capture
jQuery().mousemove(function(e) {
// add mouse coords
_ctx.collectInt(e.clientX, 16);
_ctx.collectInt(e.clientY, 16);
});
// set up keyboard entropy capture
jQuery().keypress(function(e) {
_ctx.collectInt(e.charCode, 8);
});
}
}
/* Random API */
if(!forge.random) {
forge.random = _ctx;
} else {
// extend forge.random with _ctx
for(var key in _ctx) {
forge.random[key] = _ctx[key];
}
}
// expose spawn PRNG
forge.random.createInstance = spawnPrng;
module.exports = forge.random;
})(typeof(jQuery) !== 'undefined' ? jQuery : null);
})();