random-seed-generator
Version:
Random generator for strings, secrets and numbers – with built in crypto module and seed generation for predictable outputs
245 lines (222 loc) • 7.61 kB
JavaScript
// We use cryptographically strong PRNGs (crypto.getRandomBytes() on the server,
// window.crypto.getRandomValues() in the browser) when available. If these
// PRNGs fail, we fall back to the Alea PRNG, which is not cryptographically
// strong, and we seed it with various sources such as the date, Math.random,
// and window size on the client. When using crypto.getRandomValues(), our
// primitive is hexString(), from which we construct fraction(). When using
// window.crypto.getRandomValues() or alea, the primitive is fraction and we use
// that to construct hex string.
var nodeCrypto = require('crypto');
// see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript
// for a full discussion and Alea implementation.
var Alea = function () {
function Mash() {
var n = 0xefc8249d;
var mash = function(data) {
data = data.toString();
for (var i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
var h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
mash.version = 'Mash 0.9';
return mash;
}
return (function (args) {
var s0 = 0;
var s1 = 0;
var s2 = 0;
var c = 1;
if (args.length == 0) {
args = [+new Date];
}
var mash = Mash();
s0 = mash(' ');
s1 = mash(' ');
s2 = mash(' ');
for (var i = 0; i < args.length; i++) {
s0 -= mash(args[i]);
if (s0 < 0) {
s0 += 1;
}
s1 -= mash(args[i]);
if (s1 < 0) {
s1 += 1;
}
s2 -= mash(args[i]);
if (s2 < 0) {
s2 += 1;
}
}
mash = null;
var random = function() {
var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
s0 = s1;
s1 = s2;
return s2 = t - (c = t | 0);
};
random.uint32 = function() {
return random() * 0x100000000; // 2^32
};
random.fract53 = function() {
return random() +
(random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
};
random.version = 'Alea 0.9';
random.args = args;
return random;
} (Array.prototype.slice.call(arguments)));
};
// If seeds are provided, then the alea PRNG will be used, since cryptographic
// PRNGs (Node crypto and window.crypto.getRandomValues) don't allow us to
// specify seeds. The caller is responsible for making sure to provide a seed
// for alea if a csprng is not available.
var RandomGenerator = function (seedArray) {
var self = this;
if (seedArray !== undefined)
self.alea = Alea.apply(null, seedArray);
};
RandomGenerator.prototype.fraction = function () {
var self = this;
if (self.alea) {
return self.alea();
} else if (nodeCrypto) {
var numerator = parseInt(self.hexString(8), 16);
return numerator * 2.3283064365386963e-10; // 2^-32
} else if (typeof window !== "undefined" && window.crypto &&
window.crypto.getRandomValues) {
var array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return array[0] * 2.3283064365386963e-10; // 2^-32
} else {
throw new Error('No random generator available');
}
};
RandomGenerator.prototype.hexString = function (digits) {
var self = this;
if (nodeCrypto && ! self.alea) {
var numBytes = Math.ceil(digits / 2);
var bytes;
// Try to get cryptographically strong randomness. Fall back to
// non-cryptographically strong if not available.
try {
bytes = nodeCrypto.randomBytes(numBytes);
} catch (e) {
// XXX should re-throw any error except insufficient entropy
bytes = nodeCrypto.pseudoRandomBytes(numBytes);
}
var result = bytes.toString("hex");
// If the number of digits is odd, we'll have generated an extra 4 bits
// of randomness, so we need to trim the last digit.
return result.substring(0, digits);
} else {
var hexDigits = [];
for (var i = 0; i < digits; ++i) {
hexDigits.push(self.choice("0123456789abcdef"));
}
return hexDigits.join('');
}
};
RandomGenerator.prototype._randomString = function (charsCount,
alphabet) {
var self = this;
var digits = [];
for (var i = 0; i < charsCount; i++) {
digits[i] = self.choice(alphabet);
}
return digits.join("");
};
RandomGenerator.prototype.string = function (obj) {
// 17 characters is around 96 bits of entropy, which is the amount of
// state in the Alea PRNG.
var charsCount = 17;
var UNMISTAKABLE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()[]";
if (!!obj && typeof obj === 'object' && obj !== null) {
if (obj.length && typeof obj.length === 'number') {
charsCount = obj.length;
}
if (obj.pool && typeof obj.pool === 'string') {
UNMISTAKABLE_CHARS = obj.pool;
}
}
var self = this;
return self._randomString(charsCount, UNMISTAKABLE_CHARS);
};
RandomGenerator.prototype.number = function (obj) {
var charsCount = 8;
var UNMISTAKABLE_CHARS = "0123456789";
if (!!obj && typeof obj === 'object' && !Array.isArray(obj) && obj !== null) {
if (obj.length && typeof obj.length === 'number') {
charsCount = obj.length < 20 ? obj.length : 19;
}
}
var self = this;
var num = self._randomString(charsCount, UNMISTAKABLE_CHARS);
if (num[0] === '0') {
num = '1' + num.substr(1, num.length);
}
return parseInt(num, 10);
};
RandomGenerator.prototype.secret = function (obj) {
var charsCount = 43;
var BASE64_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789-_";
if (!!obj && typeof obj === 'object' && obj !== null) {
if (obj.length && typeof obj.length === 'number') {
charsCount = obj.length;
}
if (obj.pool && typeof obj.pool === 'string') {
BASE64_CHARS = obj.pool;
}
}
var self = this;
// Default to 256 bits of entropy, or 43 characters at 6 bits per
// character.
return self._randomString(charsCount, BASE64_CHARS);
};
RandomGenerator.prototype.choice = function (arrayOrString) {
var index = Math.floor(this.fraction() * arrayOrString.length);
if (typeof arrayOrString === "string")
return arrayOrString.substr(index, 1);
else
return arrayOrString[index];
};
// instantiate RNG. Heuristically collect entropy from various sources when a
// cryptographic PRNG isn't available.
// client sources
var height = (typeof window !== 'undefined' && window.innerHeight) ||
(typeof document !== 'undefined'
&& document.documentElement
&& document.documentElement.clientHeight) ||
(typeof document !== 'undefined'
&& document.body
&& document.body.clientHeight) ||
1;
var width = (typeof window !== 'undefined' && window.innerWidth) ||
(typeof document !== 'undefined'
&& document.documentElement
&& document.documentElement.clientWidth) ||
(typeof document !== 'undefined'
&& document.body
&& document.body.clientWidth) ||
1;
var agent = (typeof navigator !== 'undefined' && navigator.userAgent) || "";
if (nodeCrypto ||
(typeof window !== "undefined" &&
window.crypto && window.crypto.getRandomValues))
module.exports = Random = new RandomGenerator();
else
module.exports = Random = new RandomGenerator([new Date(), height, width, agent, Math.random()]);
Random.createWithSeeds = function () {
if (arguments.length === 0) {
throw new Error('No seeds were provided');
}
return new RandomGenerator(arguments);
};