random.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /** @fileOverview Random number generator.
  2. *
  3. * @author Emily Stark
  4. * @author Mike Hamburg
  5. * @author Dan Boneh
  6. */
  7. /** @namespace Random number generator
  8. *
  9. * @description
  10. * <p>
  11. * This random number generator is a derivative of Ferguson and Schneier's
  12. * generator Fortuna. It collects entropy from various events into several
  13. * pools, implemented by streaming SHA-256 instances. It differs from
  14. * ordinary Fortuna in a few ways, though.
  15. * </p>
  16. *
  17. * <p>
  18. * Most importantly, it has an entropy estimator. This is present because
  19. * there is a strong conflict here between making the generator available
  20. * as soon as possible, and making sure that it doesn't "run on empty".
  21. * In Fortuna, there is a saved state file, and the system is likely to have
  22. * time to warm up.
  23. * </p>
  24. *
  25. * <p>
  26. * Second, because users are unlikely to stay on the page for very long,
  27. * and to speed startup time, the number of pools increases logarithmically:
  28. * a new pool is created when the previous one is actually used for a reseed.
  29. * This gives the same asymptotic guarantees as Fortuna, but gives more
  30. * entropy to early reseeds.
  31. * </p>
  32. *
  33. * <p>
  34. * The entire mechanism here feels pretty klunky. Furthermore, there are
  35. * several improvements that should be made, including support for
  36. * dedicated cryptographic functions that may be present in some browsers;
  37. * state files in local storage; cookies containing randomness; etc. So
  38. * look for improvements in future versions.
  39. * </p>
  40. */
  41. sjcl.random = {
  42. /** Generate several random words, and return them in an array
  43. * @param {Number} nwords The number of words to generate.
  44. */
  45. randomWords: function (nwords, paranoia) {
  46. var out = [], i, readiness = this.isReady(paranoia), g;
  47. if (readiness === this._NOT_READY) {
  48. throw new sjcl.exception.notReady("generator isn't seeded");
  49. } else if (readiness & this._REQUIRES_RESEED) {
  50. this._reseedFromPools(!(readiness & this._READY));
  51. }
  52. for (i=0; i<nwords; i+= 4) {
  53. if ((i+1) % this._MAX_WORDS_PER_BURST === 0) {
  54. this._gate();
  55. }
  56. g = this._gen4words();
  57. out.push(g[0],g[1],g[2],g[3]);
  58. }
  59. this._gate();
  60. return out.slice(0,nwords);
  61. },
  62. setDefaultParanoia: function (paranoia) {
  63. this._defaultParanoia = paranoia;
  64. },
  65. /**
  66. * Add entropy to the pools.
  67. * @param data The entropic value. Should be a 32-bit integer, array of 32-bit integers, or string
  68. * @param {Number} estimatedEntropy The estimated entropy of data, in bits
  69. * @param {String} source The source of the entropy, eg "mouse"
  70. */
  71. addEntropy: function (data, estimatedEntropy, source) {
  72. source = source || "user";
  73. var id,
  74. i, tmp,
  75. t = (new Date()).valueOf(),
  76. robin = this._robins[source],
  77. oldReady = this.isReady(), err = 0;
  78. id = this._collectorIds[source];
  79. if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; }
  80. if (robin === undefined) { robin = this._robins[source] = 0; }
  81. this._robins[source] = ( this._robins[source] + 1 ) % this._pools.length;
  82. switch(typeof(data)) {
  83. case "number":
  84. if (estimatedEntropy === undefined) {
  85. estimatedEntropy = 1;
  86. }
  87. this._pools[robin].update([id,this._eventId++,1,estimatedEntropy,t,1,data|0]);
  88. break;
  89. case "object":
  90. var objName = Object.prototype.toString.call(data);
  91. if (objName === "[object Uint32Array]") {
  92. tmp = [];
  93. for (i = 0; i < data.length; i++) {
  94. tmp.push(data[i]);
  95. }
  96. data = tmp;
  97. } else {
  98. if (objName !== "[object Array]") {
  99. err = 1;
  100. }
  101. for (i=0; i<data.length && !err; i++) {
  102. if (typeof(data[i]) != "number") {
  103. err = 1;
  104. }
  105. }
  106. }
  107. if (!err) {
  108. if (estimatedEntropy === undefined) {
  109. /* horrible entropy estimator */
  110. estimatedEntropy = 0;
  111. for (i=0; i<data.length; i++) {
  112. tmp= data[i];
  113. while (tmp>0) {
  114. estimatedEntropy++;
  115. tmp = tmp >>> 1;
  116. }
  117. }
  118. }
  119. this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data));
  120. }
  121. break;
  122. case "string":
  123. if (estimatedEntropy === undefined) {
  124. /* English text has just over 1 bit per character of entropy.
  125. * But this might be HTML or something, and have far less
  126. * entropy than English... Oh well, let's just say one bit.
  127. */
  128. estimatedEntropy = data.length;
  129. }
  130. this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);
  131. this._pools[robin].update(data);
  132. break;
  133. default:
  134. err=1;
  135. }
  136. if (err) {
  137. throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");
  138. }
  139. /* record the new strength */
  140. this._poolEntropy[robin] += estimatedEntropy;
  141. this._poolStrength += estimatedEntropy;
  142. /* fire off events */
  143. if (oldReady === this._NOT_READY) {
  144. if (this.isReady() !== this._NOT_READY) {
  145. this._fireEvent("seeded", Math.max(this._strength, this._poolStrength));
  146. }
  147. this._fireEvent("progress", this.getProgress());
  148. }
  149. },
  150. /** Is the generator ready? */
  151. isReady: function (paranoia) {
  152. var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ];
  153. if (this._strength && this._strength >= entropyRequired) {
  154. return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ?
  155. this._REQUIRES_RESEED | this._READY :
  156. this._READY;
  157. } else {
  158. return (this._poolStrength >= entropyRequired) ?
  159. this._REQUIRES_RESEED | this._NOT_READY :
  160. this._NOT_READY;
  161. }
  162. },
  163. /** Get the generator's progress toward readiness, as a fraction */
  164. getProgress: function (paranoia) {
  165. var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ];
  166. if (this._strength >= entropyRequired) {
  167. return 1.0;
  168. } else {
  169. return (this._poolStrength > entropyRequired) ?
  170. 1.0 :
  171. this._poolStrength / entropyRequired;
  172. }
  173. },
  174. /** start the built-in entropy collectors */
  175. startCollectors: function () {
  176. if (this._collectorsStarted) { return; }
  177. if (window.addEventListener) {
  178. window.addEventListener("load", this._loadTimeCollector, false);
  179. window.addEventListener("mousemove", this._mouseCollector, false);
  180. } else if (document.attachEvent) {
  181. document.attachEvent("onload", this._loadTimeCollector);
  182. document.attachEvent("onmousemove", this._mouseCollector);
  183. }
  184. else {
  185. throw new sjcl.exception.bug("can't attach event");
  186. }
  187. this._collectorsStarted = true;
  188. },
  189. /** stop the built-in entropy collectors */
  190. stopCollectors: function () {
  191. if (!this._collectorsStarted) { return; }
  192. if (window.removeEventListener) {
  193. window.removeEventListener("load", this._loadTimeCollector, false);
  194. window.removeEventListener("mousemove", this._mouseCollector, false);
  195. } else if (window.detachEvent) {
  196. window.detachEvent("onload", this._loadTimeCollector);
  197. window.detachEvent("onmousemove", this._mouseCollector);
  198. }
  199. this._collectorsStarted = false;
  200. },
  201. /* use a cookie to store entropy.
  202. useCookie: function (all_cookies) {
  203. throw new sjcl.exception.bug("random: useCookie is unimplemented");
  204. },*/
  205. /** add an event listener for progress or seeded-ness. */
  206. addEventListener: function (name, callback) {
  207. this._callbacks[name][this._callbackI++] = callback;
  208. },
  209. /** remove an event listener for progress or seeded-ness */
  210. removeEventListener: function (name, cb) {
  211. var i, j, cbs=this._callbacks[name], jsTemp=[];
  212. /* I'm not sure if this is necessary; in C++, iterating over a
  213. * collection and modifying it at the same time is a no-no.
  214. */
  215. for (j in cbs) {
  216. if (cbs.hasOwnProperty(j) && cbs[j] === cb) {
  217. jsTemp.push(j);
  218. }
  219. }
  220. for (i=0; i<jsTemp.length; i++) {
  221. j = jsTemp[i];
  222. delete cbs[j];
  223. }
  224. },
  225. /* private */
  226. _pools : [new sjcl.hash.sha256()],
  227. _poolEntropy : [0],
  228. _reseedCount : 0,
  229. _robins : {},
  230. _eventId : 0,
  231. _collectorIds : {},
  232. _collectorIdNext : 0,
  233. _strength : 0,
  234. _poolStrength : 0,
  235. _nextReseed : 0,
  236. _key : [0,0,0,0,0,0,0,0],
  237. _counter : [0,0,0,0],
  238. _cipher : undefined,
  239. _defaultParanoia : 6,
  240. /* event listener stuff */
  241. _collectorsStarted : false,
  242. _callbacks : {progress: {}, seeded: {}},
  243. _callbackI : 0,
  244. /* constants */
  245. _NOT_READY : 0,
  246. _READY : 1,
  247. _REQUIRES_RESEED : 2,
  248. _MAX_WORDS_PER_BURST : 65536,
  249. _PARANOIA_LEVELS : [0,48,64,96,128,192,256,384,512,768,1024],
  250. _MILLISECONDS_PER_RESEED : 30000,
  251. _BITS_PER_RESEED : 80,
  252. /** Generate 4 random words, no reseed, no gate.
  253. * @private
  254. */
  255. _gen4words: function () {
  256. for (var i=0; i<4; i++) {
  257. this._counter[i] = this._counter[i]+1 | 0;
  258. if (this._counter[i]) { break; }
  259. }
  260. return this._cipher.encrypt(this._counter);
  261. },
  262. /* Rekey the AES instance with itself after a request, or every _MAX_WORDS_PER_BURST words.
  263. * @private
  264. */
  265. _gate: function () {
  266. this._key = this._gen4words().concat(this._gen4words());
  267. this._cipher = new sjcl.cipher.aes(this._key);
  268. },
  269. /** Reseed the generator with the given words
  270. * @private
  271. */
  272. _reseed: function (seedWords) {
  273. this._key = sjcl.hash.sha256.hash(this._key.concat(seedWords));
  274. this._cipher = new sjcl.cipher.aes(this._key);
  275. for (var i=0; i<4; i++) {
  276. this._counter[i] = this._counter[i]+1 | 0;
  277. if (this._counter[i]) { break; }
  278. }
  279. },
  280. /** reseed the data from the entropy pools
  281. * @param full If set, use all the entropy pools in the reseed.
  282. */
  283. _reseedFromPools: function (full) {
  284. var reseedData = [], strength = 0, i;
  285. this._nextReseed = reseedData[0] =
  286. (new Date()).valueOf() + this._MILLISECONDS_PER_RESEED;
  287. for (i=0; i<16; i++) {
  288. /* On some browsers, this is cryptographically random. So we might
  289. * as well toss it in the pot and stir...
  290. */
  291. reseedData.push(Math.random()*0x100000000|0);
  292. }
  293. for (i=0; i<this._pools.length; i++) {
  294. reseedData = reseedData.concat(this._pools[i].finalize());
  295. strength += this._poolEntropy[i];
  296. this._poolEntropy[i] = 0;
  297. if (!full && (this._reseedCount & (1<<i))) { break; }
  298. }
  299. /* if we used the last pool, push a new one onto the stack */
  300. if (this._reseedCount >= 1 << this._pools.length) {
  301. this._pools.push(new sjcl.hash.sha256());
  302. this._poolEntropy.push(0);
  303. }
  304. /* how strong was this reseed? */
  305. this._poolStrength -= strength;
  306. if (strength > this._strength) {
  307. this._strength = strength;
  308. }
  309. this._reseedCount ++;
  310. this._reseed(reseedData);
  311. },
  312. _mouseCollector: function (ev) {
  313. var x = ev.x || ev.clientX || ev.offsetX, y = ev.y || ev.clientY || ev.offsetY;
  314. sjcl.random.addEntropy([x,y], 2, "mouse");
  315. },
  316. _loadTimeCollector: function (ev) {
  317. sjcl.random.addEntropy((new Date()).valueOf(), 2, "loadtime");
  318. },
  319. _fireEvent: function (name, arg) {
  320. var j, cbs=sjcl.random._callbacks[name], cbsTemp=[];
  321. /* TODO: there is a race condition between removing collectors and firing them */
  322. /* I'm not sure if this is necessary; in C++, iterating over a
  323. * collection and modifying it at the same time is a no-no.
  324. */
  325. for (j in cbs) {
  326. if (cbs.hasOwnProperty(j)) {
  327. cbsTemp.push(cbs[j]);
  328. }
  329. }
  330. for (j=0; j<cbsTemp.length; j++) {
  331. cbsTemp[j](arg);
  332. }
  333. }
  334. };
  335. (function(){
  336. try {
  337. // get cryptographically strong entropy in Webkit
  338. var ab = new Uint32Array(32);
  339. crypto.getRandomValues(ab);
  340. sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues");
  341. } catch (e) {
  342. // no getRandomValues :-(
  343. }
  344. })();