initial commit for GitHub
This commit is contained in:
368
core/random.js
Normal file
368
core/random.js
Normal file
@@ -0,0 +1,368 @@
|
||||
/** @fileOverview Random number generator.
|
||||
*
|
||||
* @author Emily Stark
|
||||
* @author Mike Hamburg
|
||||
* @author Dan Boneh
|
||||
*/
|
||||
|
||||
/** @namespace Random number generator
|
||||
*
|
||||
* @description
|
||||
* <p>
|
||||
* This random number generator is a derivative of Ferguson and Schneier's
|
||||
* generator Fortuna. It collects entropy from various events into several
|
||||
* pools, implemented by streaming SHA-256 instances. It differs from
|
||||
* ordinary Fortuna in a few ways, though.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Most importantly, it has an entropy estimator. This is present because
|
||||
* there is a strong conflict here between making the generator available
|
||||
* as soon as possible, and making sure that it doesn't "run on empty".
|
||||
* In Fortuna, there is a saved state file, and the system is likely to have
|
||||
* time to warm up.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Second, because users are unlikely to stay on the page for very long,
|
||||
* and to speed startup time, the number of pools increases logarithmically:
|
||||
* a new pool is created when the previous one is actually used for a reseed.
|
||||
* This gives the same asymptotic guarantees as Fortuna, but gives more
|
||||
* entropy to early reseeds.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The entire mechanism here feels pretty klunky. Furthermore, there are
|
||||
* several improvements that should be made, including support for
|
||||
* dedicated cryptographic functions that may be present in some browsers;
|
||||
* state files in local storage; cookies containing randomness; etc. So
|
||||
* look for improvements in future versions.
|
||||
* </p>
|
||||
*/
|
||||
sjcl.random = {
|
||||
/** Generate several random words, and return them in an array
|
||||
* @param {Number} nwords The number of words to generate.
|
||||
*/
|
||||
randomWords: function (nwords, paranoia) {
|
||||
var out = [], i, readiness = this.isReady(paranoia), g;
|
||||
|
||||
if (readiness === this._NOT_READY) {
|
||||
throw new sjcl.exception.notready("generator isn't seeded");
|
||||
} else if (readiness & this._REQUIRES_RESEED) {
|
||||
this._reseedFromPools(!(readiness & this._READY));
|
||||
}
|
||||
|
||||
for (i=0; i<nwords; i+= 4) {
|
||||
if ((i+1) % this._MAX_WORDS_PER_BURST === 0) {
|
||||
this._gate();
|
||||
}
|
||||
|
||||
g = this._gen4words();
|
||||
out.push(g[0],g[1],g[2],g[3]);
|
||||
}
|
||||
this._gate();
|
||||
|
||||
return out.slice(0,nwords);
|
||||
},
|
||||
|
||||
setDefaultParanoia: function (paranoia) {
|
||||
this._defaultParanoia = paranoia;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add entropy to the pools.
|
||||
* @param data The entropic value. Should be a 32-bit integer, array of 32-bit integers, or string
|
||||
* @param {Number} estimatedEntropy The estimated entropy of data, in bits
|
||||
* @param {String} source The source of the entropy, eg "mouse"
|
||||
*/
|
||||
addEntropy: function (data, estimatedEntropy, source) {
|
||||
source = source || "user";
|
||||
|
||||
var id,
|
||||
i, ty = 0, tmp,
|
||||
t = (new Date()).valueOf(),
|
||||
robin = this._robins[source],
|
||||
oldReady = this.isReady();
|
||||
|
||||
id = this._collectorIds[source];
|
||||
if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; }
|
||||
|
||||
if (robin === undefined) { robin = this._robins[source] = 0; }
|
||||
this._robins[source] = ( this._robins[source] + 1 ) % this._pools.length;
|
||||
|
||||
switch(typeof(data)) {
|
||||
|
||||
case "number":
|
||||
data=[data];
|
||||
ty=1;
|
||||
break;
|
||||
|
||||
case "object":
|
||||
if (estimatedEntropy === undefined) {
|
||||
/* horrible entropy estimator */
|
||||
estimatedEntropy = 0;
|
||||
for (i=0; i<data.length; i++) {
|
||||
tmp= data[i];
|
||||
while (tmp>0) {
|
||||
estimatedEntropy++;
|
||||
tmp = tmp >>> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._pools[robin].update([id,this._eventId++,ty||2,estimatedEntropy,t,data.length].concat(data));
|
||||
break;
|
||||
|
||||
case "string":
|
||||
if (estimatedEntropy === undefined) {
|
||||
/* English text has just over 1 bit per character of entropy.
|
||||
* But this might be HTML or something, and have far less
|
||||
* entropy than English... Oh well, let's just say one bit.
|
||||
*/
|
||||
estimatedEntropy = data.length;
|
||||
}
|
||||
this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);
|
||||
this._pools[robin].update(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
throw new sjcl.exception.bug("random: addEntropy only supports number, array or string");
|
||||
}
|
||||
|
||||
/* record the new strength */
|
||||
this._poolEntropy[robin] += estimatedEntropy;
|
||||
this._poolStrength += estimatedEntropy;
|
||||
|
||||
/* fire off events */
|
||||
if (oldReady === this._NOT_READY) {
|
||||
if (this.isReady() !== this._NOT_READY) {
|
||||
this._fireEvent("seeded", Math.max(this._strength, this._poolStrength));
|
||||
}
|
||||
this._fireEvent("progress", this.getProgress());
|
||||
}
|
||||
},
|
||||
|
||||
/** Is the generator ready? */
|
||||
isReady: function (paranoia) {
|
||||
var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ];
|
||||
|
||||
if (this._strength && this._strength >= entropyRequired) {
|
||||
return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ?
|
||||
this._REQUIRES_RESEED | this._READY :
|
||||
this._READY;
|
||||
} else {
|
||||
return (this._poolStrength >= entropyRequired) ?
|
||||
this._REQUIRES_RESEED | this._NOT_READY :
|
||||
this._NOT_READY;
|
||||
}
|
||||
},
|
||||
|
||||
/** Get the generator's progress toward readiness, as a fraction */
|
||||
getProgress: function (paranoia) {
|
||||
var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ];
|
||||
|
||||
if (this._strength >= entropyRequired) {
|
||||
return 1.0;
|
||||
} else {
|
||||
return (this._poolStrength > entropyRequired) ?
|
||||
1.0 :
|
||||
this._poolStrength / entropyRequired;
|
||||
}
|
||||
},
|
||||
|
||||
/** start the built-in entropy collectors */
|
||||
startCollectors: function () {
|
||||
if (this._collectorsStarted) { return; }
|
||||
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener("load", this._loadTimeCollector, false);
|
||||
window.addEventListener("mousemove", this._mouseCollector, false);
|
||||
} else if (document.attachEvent) {
|
||||
document.attachEvent("onload", this._loadTimeCollector);
|
||||
document.attachEvent("onmousemove", this._mouseCollector);
|
||||
}
|
||||
else {
|
||||
throw new sjcl.exception.bug("can't attach event");
|
||||
}
|
||||
|
||||
this._collectorsStarted = true;
|
||||
},
|
||||
|
||||
/** stop the built-in entropy collectors */
|
||||
stopCollectors: function () {
|
||||
if (!this._collectorsStarted) { return; }
|
||||
|
||||
if (window.removeEventListener) {
|
||||
window.removeEventListener("load", this._loadTimeCollector);
|
||||
window.removeEventListener("mousemove", this._mouseCollector);
|
||||
} else if (window.detachEvent) {
|
||||
window.detachEvent("onload", this._loadTimeCollector);
|
||||
window.detachEvent("onmousemove", this._mouseCollector);
|
||||
}
|
||||
this._collectorsStarted = false;
|
||||
},
|
||||
|
||||
/* use a cookie to store entropy.
|
||||
useCookie: function (all_cookies) {
|
||||
throw new sjcl.exception.bug("random: useCookie is unimplemented");
|
||||
},*/
|
||||
|
||||
/** add an event listener for progress or seeded-ness. */
|
||||
addEventListener: function (name, callback) {
|
||||
this._callbacks[name][this._callbackI++] = callback;
|
||||
},
|
||||
|
||||
/** remove an event listener for progress or seeded-ness */
|
||||
removeEventListener: function (name, cb) {
|
||||
var i, j, cbs=this._callbacks[name], jsTemp=[];
|
||||
|
||||
/* I'm not sure if this is necessary; in C++, iterating over a
|
||||
* collection and modifying it at the same time is a no-no.
|
||||
*/
|
||||
|
||||
for (j in cbs) {
|
||||
if (cbs.hasOwnProperty[j] && cbs[j] === cb) {
|
||||
jsTemp.push(j);
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<jsTemp.length; i++) {
|
||||
j = jsTemp[i];
|
||||
delete cbs[j];
|
||||
}
|
||||
},
|
||||
|
||||
/* private */
|
||||
_pools : [new sjcl.hash.sha256()],
|
||||
_poolEntropy : [0],
|
||||
_reseedCount : 0,
|
||||
_robins : {},
|
||||
_eventId : 0,
|
||||
|
||||
_collectorIds : {},
|
||||
_collectorIdNext : 0,
|
||||
|
||||
_strength : 0,
|
||||
_poolStrength : 0,
|
||||
_nextReseed : 0,
|
||||
_key : [0,0,0,0,0,0,0,0],
|
||||
_counter : [0,0,0,0],
|
||||
_cipher : undefined,
|
||||
_defaultParanoia : 6,
|
||||
|
||||
/* event listener stuff */
|
||||
_collectorsStarted : false,
|
||||
_callbacks : {progress: {}, seeded: {}},
|
||||
_callbackI : 0,
|
||||
|
||||
/* constants */
|
||||
_NOT_READY : 0,
|
||||
_READY : 1,
|
||||
_REQUIRES_RESEED : 2,
|
||||
|
||||
_MAX_WORDS_PER_BURST : 65536,
|
||||
_PARANOIA_LEVELS : [0,48,64,96,128,192,256,384,512,768,1024],
|
||||
_MILLISECONDS_PER_RESEED : 30000,
|
||||
_BITS_PER_RESEED : 80,
|
||||
|
||||
/** Generate 4 random words, no reseed, no gate.
|
||||
* @private
|
||||
*/
|
||||
_gen4words: function () {
|
||||
for (var i=0; i<4; i++) {
|
||||
this._counter[i] = this._counter[i]+1 | 0;
|
||||
if (this._counter[i]) { break; }
|
||||
}
|
||||
return this._cipher.encrypt(this._counter);
|
||||
},
|
||||
|
||||
/* Rekey the AES instance with itself after a request, or every _MAX_WORDS_PER_BURST words.
|
||||
* @private
|
||||
*/
|
||||
_gate: function () {
|
||||
this._key = this._gen4words().concat(this._gen4words());
|
||||
this._cipher = new sjcl.cipher.aes(this._key);
|
||||
},
|
||||
|
||||
/** Reseed the generator with the given words
|
||||
* @private
|
||||
*/
|
||||
_reseed: function (seedWords) {
|
||||
this._key = sjcl.hash.sha256.hash(this._key.concat(seedWords));
|
||||
this._cipher = new sjcl.cipher.aes(this._key);
|
||||
for (var i=0; i<4; i++) {
|
||||
this._counter[i] = this._counter[i]+1 | 0;
|
||||
if (this._counter[i]) { break; }
|
||||
}
|
||||
},
|
||||
|
||||
/** reseed the data from the entropy pools
|
||||
* @param full If set, use all the entropy pools in the reseed.
|
||||
*/
|
||||
_reseedFromPools: function (full) {
|
||||
var reseedData = [], strength = 0, i;
|
||||
|
||||
this._nextReseed = reseedData[0] =
|
||||
(new Date()).valueOf() + this._MILLISECONDS_PER_RESEED;
|
||||
|
||||
for (i=0; i<16; i++) {
|
||||
/* On some browsers, this is cryptographically random. So we might
|
||||
* as well toss it in the pot and stir...
|
||||
*/
|
||||
reseedData.push(Math.random()*0x100000000|0);
|
||||
}
|
||||
|
||||
for (i=0; i<this._pools.length; i++) {
|
||||
reseedData = reseedData.concat(this._pools[i].finalize());
|
||||
strength += this._poolEntropy[i];
|
||||
this._poolEntropy[i] = 0;
|
||||
|
||||
if (!full && (this._reseedCount & (1<<i))) { break; }
|
||||
}
|
||||
|
||||
/* if we used the last pool, push a new one onto the stack */
|
||||
if (this._reseedCount >= 1 << this._pools.length) {
|
||||
this._pools.push(new sjcl.hash.sha256());
|
||||
this._poolEntropy.push(0);
|
||||
}
|
||||
|
||||
/* how strong was this reseed? */
|
||||
this._poolStrength -= strength;
|
||||
if (strength > this._strength) {
|
||||
this._strength = strength;
|
||||
}
|
||||
|
||||
this._reseedCount ++;
|
||||
this._reseed(reseedData);
|
||||
},
|
||||
|
||||
_mouseCollector: function (ev) {
|
||||
var x = ev.x || ev.clientX || ev.offsetX, y = ev.y || ev.clientY || ev.offsetY;
|
||||
sjcl.random.addEntropy([x,y], 2, "mouse");
|
||||
},
|
||||
|
||||
_loadTimeCollector: function (ev) {
|
||||
var d = new Date();
|
||||
sjcl.random.addEntropy(d, 2, "loadtime");
|
||||
},
|
||||
|
||||
_fireEvent: function (name, arg) {
|
||||
var j, cbs=sjcl.random._callbacks[name], cbsTemp=[];
|
||||
/* TODO: there is a race condition between removing collectors and firing them */
|
||||
|
||||
/* I'm not sure if this is necessary; in C++, iterating over a
|
||||
* collection and modifying it at the same time is a no-no.
|
||||
*/
|
||||
|
||||
for (j in cbs) {
|
||||
if (cbs.hasOwnProperty(j)) {
|
||||
cbsTemp.push(cbs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
for (j=0; j<cbsTemp.length; j++) {
|
||||
cbsTemp[j](arg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user