|
40 | 40 | */ |
41 | 41 | var bcrypt = {}; |
42 | 42 |
|
| 43 | + /** |
| 44 | + * The random implementation to use as a fallback. |
| 45 | + * @type {?function(number):!Array.<number>} |
| 46 | + * @inner |
| 47 | + */ |
| 48 | + var randomFallback = null; |
| 49 | + |
43 | 50 | /** |
44 | 51 | * Generates cryptographically secure random bytes. |
45 | 52 | * @function |
|
62 | 69 | return randomFallback(len); |
63 | 70 | } |
64 | 71 |
|
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) { |
71 | 81 | for (var a=[], i=0; i<len; ++i) |
72 | 82 | a[i] = ((0.5 + isaac() * 2.3283064365386963e-10) * 256) | 0; |
73 | 83 | return a; |
74 | 84 | }; |
75 | 85 |
|
76 | 86 | /** |
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! |
79 | 89 | * @param {?function(number):!Array.<number>} random Function taking the number of bytes to generate as its |
80 | 90 | * sole argument, returning the corresponding array of cryptographically secure random byte values. |
81 | 91 | * @see http://nodejs.org/api/crypto.html |
|
1162 | 1172 | } |
1163 | 1173 | } |
1164 | 1174 |
|
| 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 | + |
1165 | 1313 | /* |
1166 | 1314 | isaac.js Copyright (c) 2012 Yves-Marie K. Rinquin |
1167 | 1315 |
|
|
1194 | 1342 | brs = 0, // last result |
1195 | 1343 | cnt = 0, // counter |
1196 | 1344 | r = Array(256), // result array |
1197 | | - gnt = 0; // generation counter |
| 1345 | + gnt = 0, // generation counter |
| 1346 | + isd = false; // initially seeded |
1198 | 1347 |
|
1199 | | - seed(Math.random() * 0xffffffff); |
1200 | 1348 |
|
1201 | 1349 | /* 32-bit integer safe adder */ |
1202 | 1350 | function add(x, y) { |
|
1293 | 1441 |
|
1294 | 1442 | /* return a random number between */ |
1295 | 1443 | return function() { |
| 1444 | + if (!isd) // seed from accumulator |
| 1445 | + isd = true, |
| 1446 | + accum.stop(), |
| 1447 | + seed(accum.fetch()); |
1296 | 1448 | if (!gnt--) |
1297 | 1449 | prng(), gnt = 255; |
1298 | 1450 | return r[gnt]; |
|
0 commit comments