Skip to content

Commit 13aa291

Browse files
committed
Add entropy accumulator before ISAAC, see #16
1 parent a55ce18 commit 13aa291

10 files changed

Lines changed: 447 additions & 107 deletions

File tree

dist/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ The Web Crypto API is fairly new and not supported by all / older browsers. For
2323
includes [isaac.js](https://github.com/rubycon/isaac.js), a JavaScript implementation of the ISAAC PRNG, which is used
2424
as the default random fallback if neither node's crypto module nor the Web Crypto API is available.
2525

26+
**Please note:** Seeding is performed using an [entropy accumulator](https://github.com/dcodeIO/bcrypt.js/blob/master/src/bcrypt/prng/accum.js)
27+
using multiple sources of randomness like mouse and touch movement, page load time, general timings and mixing in
28+
Math.random occasionally. Please consider taking a look at the accumulator to understand how it works for optimal
29+
results.
30+
2631
* **[bcrypt-isaac.js](https://github.com/dcodeIO/bcrypt.js/blob/master/dist/bcrypt-isaac.js)**
2732
contains the commented source code.
2833

dist/bcrypt-isaac.js

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@
4040
*/
4141
var bcrypt = {};
4242

43+
/**
44+
* The random implementation to use as a fallback.
45+
* @type {?function(number):!Array.<number>}
46+
* @inner
47+
*/
48+
var randomFallback = null;
49+
4350
/**
4451
* Generates cryptographically secure random bytes.
4552
* @function
@@ -62,20 +69,23 @@
6269
return randomFallback(len);
6370
}
6471

65-
/**
66-
* The random implementation to use as a fallback.
67-
* @type {?function(number):!Array.<number>}
68-
* @inner
69-
*/
70-
var randomFallback = function(len) {
72+
// Test if any secure randomness source is available
73+
var randomAvailable = false;
74+
try {
75+
random(1);
76+
randomAvailable = true;
77+
} catch (e) {}
78+
79+
// Default fallback, if any
80+
randomFallback = function(len) {
7181
for (var a=[], i=0; i<len; ++i)
7282
a[i] = ((0.5 + isaac() * 2.3283064365386963e-10) * 256) | 0;
7383
return a;
7484
};
7585

7686
/**
77-
* Sets the random number generator to use as a fallback if neither node's `crypto` module nor the Web Crypto API
78-
* is available.
87+
* Sets the pseudo random number generator to use as a fallback if neither node's `crypto` module nor the Web Crypto
88+
* API is available. Please note: It is highly important that this PRNG is cryptographically secure!
7989
* @param {?function(number):!Array.<number>} random Function taking the number of bytes to generate as its
8090
* sole argument, returning the corresponding array of cryptographically secure random byte values.
8191
* @see http://nodejs.org/api/crypto.html
@@ -1162,6 +1172,144 @@
11621172
}
11631173
}
11641174

1175+
/* basic entropy accumulator */
1176+
var accum = (function() {
1177+
1178+
var pool, // randomness pool
1179+
time, // start timestamp
1180+
last; // last step timestamp
1181+
1182+
/* initialize with default pool */
1183+
function init() {
1184+
pool = [];
1185+
time = new Date().getTime();
1186+
last = time;
1187+
// use Math.random
1188+
pool.push((Math.random() * 0xffffffff)|0);
1189+
// use current time
1190+
pool.push(time|0);
1191+
}
1192+
1193+
/* perform one step */
1194+
function step() {
1195+
if (!to)
1196+
return;
1197+
if (pool.length >= 255) { // stop at 255 values (1 more is added on fetch)
1198+
stop();
1199+
return;
1200+
}
1201+
var now = new Date().getTime();
1202+
// use actual time difference
1203+
pool.push(now-last);
1204+
// always compute, occasionally use Math.random
1205+
var rnd = (Math.random() * 0xffffffff)|0;
1206+
if (now % 2)
1207+
pool[pool.length-1] += rnd;
1208+
last = now;
1209+
to = setTimeout(step, 100+Math.random()*512); // use hypothetical time difference
1210+
}
1211+
1212+
var to = null;
1213+
1214+
/* starts accumulating */
1215+
function start() {
1216+
if (to) return;
1217+
to = setTimeout(step, 100+Math.random()*512);
1218+
if (console.log)
1219+
console.log("bcrypt-isaac: collecting entropy...");
1220+
// install collectors
1221+
if (typeof window !== 'undefined' && window && window.addEventListener)
1222+
window.addEventListener("load", loadCollector, false),
1223+
window.addEventListener("mousemove", mouseCollector, false),
1224+
window.addEventListener("touchmove", touchCollector, false);
1225+
else if (typeof document !== 'undefined' && document && document.attachEvent)
1226+
document.attachEvent("onload", loadCollector),
1227+
document.attachEvent("onmousemove", mouseCollector);
1228+
}
1229+
1230+
/* stops accumulating */
1231+
function stop() {
1232+
if (!to) return;
1233+
clearTimeout(to); to = null;
1234+
// uninstall collectors
1235+
if (typeof window !== 'undefined' && window && window.removeEventListener)
1236+
window.removeEventListener("load", loadCollector, false),
1237+
window.removeEventListener("mousemove", mouseCollector, false),
1238+
window.removeEventListener("touchmove", touchCollector, false);
1239+
else if (typeof document !== 'undefined' && document && document.detachEvent)
1240+
document.detachEvent("onload", loadCollector),
1241+
document.detachEvent("onmousemove", mouseCollector);
1242+
}
1243+
1244+
/* fetches the randomness pool */
1245+
function fetch() {
1246+
// add overall time difference
1247+
pool.push((new Date().getTime()-time)|0);
1248+
var res = pool;
1249+
init();
1250+
if (console.log)
1251+
console.log("bcrypt-isaac: using "+res.length+"/256 samples of entropy");
1252+
console.log(res);
1253+
return res;
1254+
}
1255+
1256+
/* adds the current time to the top of the pool */
1257+
function addTime() {
1258+
pool[pool.length-1] += new Date().getTime() - time;
1259+
}
1260+
1261+
/* page load collector */
1262+
function loadCollector() {
1263+
if (!to || pool.length >= 255)
1264+
return;
1265+
pool.push(0);
1266+
addTime();
1267+
}
1268+
1269+
/* mouse events collector */
1270+
function mouseCollector(ev) {
1271+
if (!to || pool.length >= 255)
1272+
return;
1273+
try {
1274+
var x = ev.x || ev.clientX || ev.offsetX || 0,
1275+
y = ev.y || ev.clientY || ev.offsetY || 0;
1276+
if (x != 0 || y != 0)
1277+
pool[pool.length-1] += ((x-mouseCollector.last[0]) ^ (y-mouseCollector.last[1])),
1278+
addTime(),
1279+
mouseCollector.last = [x,y];
1280+
} catch (e) {}
1281+
}
1282+
mouseCollector.last = [0,0];
1283+
1284+
/* touch events collector */
1285+
function touchCollector(ev) {
1286+
if (!to || pool.length >= 255)
1287+
return;
1288+
try {
1289+
var touch = ev.touches[0] || ev.changedTouches[0];
1290+
var x = touch.pageX || touch.clientX || 0,
1291+
y = touch.pageY || touch.clientY || 0;
1292+
if (x != 0 || y != 0)
1293+
pool[pool.length-1] += (x-touchCollector.last[0]) ^ (y-touchCollector.last[1]),
1294+
addTime(),
1295+
touchCollector.last = [x,y];
1296+
} catch (e) {}
1297+
}
1298+
touchCollector.last = [0,0];
1299+
1300+
init();
1301+
return {
1302+
"start": start,
1303+
"stop": stop,
1304+
"fetch": fetch
1305+
}
1306+
1307+
})();
1308+
1309+
// Start accumulating
1310+
if (!randomAvailable)
1311+
accum.start();
1312+
11651313
/*
11661314
isaac.js Copyright (c) 2012 Yves-Marie K. Rinquin
11671315
@@ -1194,9 +1342,9 @@
11941342
brs = 0, // last result
11951343
cnt = 0, // counter
11961344
r = Array(256), // result array
1197-
gnt = 0; // generation counter
1345+
gnt = 0, // generation counter
1346+
isd = false; // initially seeded
11981347

1199-
seed(Math.random() * 0xffffffff);
12001348

12011349
/* 32-bit integer safe adder */
12021350
function add(x, y) {
@@ -1293,6 +1441,10 @@
12931441

12941442
/* return a random number between */
12951443
return function() {
1444+
if (!isd) // seed from accumulator
1445+
isd = true,
1446+
accum.stop(),
1447+
seed(accum.fetch());
12961448
if (!gnt--)
12971449
prng(), gnt = 255;
12981450
return r[gnt];

0 commit comments

Comments
 (0)