Skip to content

Instantly share code, notes, and snippets.

@intrnl
Last active March 10, 2022 07:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save intrnl/13e02c9429de8124ebd131e086ba1e06 to your computer and use it in GitHub Desktop.
Save intrnl/13e02c9429de8124ebd131e086ba1e06 to your computer and use it in GitHub Desktop.
Secure random number generators
/**
* https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
* @param {number} min
* @param {number} max
* @returns {number}
*/
function random (min, max) {
const range = max - min;
let rval = 0;
const bits = Math.ceil(Math.log2(range));
if (bits > 53) {
throw new Error('cannot generate random number larger than 53 bits');
}
const bytes = Math.ceil(bits / 8);
const mask = (2 ** bits) - 1;
const arr = new Uint8Array(bytes);
crypto.getRandomValues(arr);
let p = (bytes - 1) * 8;
for (let i = 0; i < bytes; i++) {
rval += arr[i] * (2 ** p);
p -= 8;
}
rval = rval & mask;
if (rval >= range) {
return random(min, max);
}
return rval + min;
}
// Cache size must be divisible by 6 because each attempt uses 6 bytes.
const randlen = 6 * 1024;
const randbuf = new Uint8Array(randlen);
let randoffset = randlen;
const RAND_MAX = 0xFFFF_FFFF_FFFF;
/**
* https://github.com/nodejs/node/blob/e8697cf/lib/internal/crypto/random.js#L203
* @param {number} min
* @param {number} max
* @returns {number}
*/
function randomint (min, max) {
const range = max - min;
if (!(range <= RAND_MAX)) {
throw new Error(`${max} - ${min} <= ${RAND_MAX}`);
}
const limit = RAND_MAX - (RAND_MAX % range);
while (true) {
if (randoffset >= randlen) {
crypto.getRandomValues(randbuf);
randoffset = 0;
}
// byte length = 6
const x = readUint48BE(randbuf, randoffset);
randoffset += 6;
if (x < limit) {
const n = (x % range) + min;
return n;
}
}
}
function readUint48BE (buf, offset) {
const first = buf[offset];
const last = buf[offset + 5];
return (
(first * 2 ** 8 + buf[++offset]) * 2 ** 32 +
buf[++offset] * 2 ** 24 +
buf[++offset] * 2 ** 16 +
buf[++offset] * 2 ** 8 +
last
);
}
/**
* https://stackoverflow.com/a/41452318
* @param {number} min
* @param {number} max
* @returns {number}
*/
function randrange (min, max) {
const range = max - min;
const bytes = Math.ceil(Math.log2(range) / 8);
if (range <= 0 || !bytes) {
return min;
}
const maxn = 256 ** bytes;
const buf = new Uint8Array(bytes);
while (true) {
crypto.getRandomValues(buf);
let val = 0;
for (let idx = 0; idx < bytes; idx++) {
val = (val << 8) + buf[idx];
}
if (val < maxn - (maxn % range)) {
return min + (val % range);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment