在 javascript 中保护随机数?

如何在 javascript 中生成加密安全的随机数?

64657 次浏览

You can for instance use mouse movement as seed for random numbers, read out time and mouse position whenever the onmousemove event happens, feed that data to a whitening function and you will have some first class random at hand. Though do make sure that user has moved the mouse sufficiently before you use the data.

Edit: I have myself played a bit with the concept by making a password generator, I wouldn't guarantee that my whitening function is flawless, but being constantly reseeded I'm pretty sure that it's plenty for the job: ebusiness.hopto.org/generator.htm

Edit2: It now sort of works with smartphones, but only by disabling touch functionality while the entropy is gathered. Android won't work properly any other way.

You might want to try http://sourceforge.net/projects/clipperzlib/ It has an implementation of Fortuna which is a cryptographically secure random number generator. (Take a look at src/js/Clipperz/Crypto/PRNG.js). It appears to use the mouse as a source of randomness as well.

There's been discussion at WHATWG on adding this to the window.crypto object. You can read the discussion and check out the proposed API and webkit bug (22049).

Just tested the following code in Chrome to get a random byte:

(function(){
var buf = new Uint8Array(1);
window.crypto.getRandomValues(buf);
alert(buf[0]);
})();

In order, I think your best bets are:

  1. window.crypto.getRandomValues or window.msCrypto.getRandomValues
  2. The sjcl library's randomWords function (http://crypto.stanford.edu/sjcl/)
  3. The isaac library's random number generator (which is seeded by Math.random, so not really cryptographically secure) (https://github.com/rubycon/isaac.js)

window.crypto.getRandomValues has been implemented in Chrome for a while now, and relatively recently in Firefox as well. Unfortunately, Internet Explorer 10 and before do not implement the function. IE 11 has window.msCrypto, which accomplishes the same thing. sjcl has a great random number generator seeded from mouse movements, but there's always a chance that either the mouse won't have moved sufficiently to seed the generator, or that the user is on a mobile device where there is no mouse movement whatsoever. Thus, I recommend having a fallback case where you can still get a non-secure random number if there is no choice. Here's how I've handled this:

function GetRandomWords (wordCount) {
var randomWords;


// First we're going to try to use a built-in CSPRNG
if (window.crypto && window.crypto.getRandomValues) {
randomWords = new Int32Array(wordCount);
window.crypto.getRandomValues(randomWords);
}
// Because of course IE calls it msCrypto instead of being standard
else if (window.msCrypto && window.msCrypto.getRandomValues) {
randomWords = new Int32Array(wordCount);
window.msCrypto.getRandomValues(randomWords);
}
// So, no built-in functionality - bummer. If the user has wiggled the mouse enough,
// sjcl might help us out here
else if (sjcl.random.isReady()) {
randomWords = sjcl.random.randomWords(wordCount);
}
// Last resort - we'll use isaac.js to get a random number. It's seeded from Math.random(),
// so this isn't ideal, but it'll still greatly increase the space of guesses a hacker would
// have to make to crack the password.
else {
randomWords = [];
for (var i = 0; i < wordCount; i++) {
randomWords.push(isaac.rand());
}
}


return randomWords;
};

You'll need to include sjcl.js and isaac.js for that implementation, and be sure to start the sjcl entropy collector as soon as your page is loaded:

sjcl.random.startCollectors();

sjcl is dual-licensed BSD and GPL, while isaac.js is MIT, so it's perfectly safe to use either of those in any project. As mentioned in another answer, clipperz is another option, however for whatever bizarre reason, it is licensed under the AGPL. I have yet to see anyone who seems to understand what implications that has for a JavaScript library, but I'd universally avoid it.

One way to improve the code I've posted might be to store the state of the isaac random number generator in localStorage, so it isn't reseeded every time the page is loaded. Isaac will generate a random sequence, but for cryptography purposes, the seed is all-important. Seeding with Math.random is bad, but at least a little less bad if it isn't necessarily on every page load.

First of all, you need a source of entropy. For example, movement of the mouse, password, or any other. But all of these sources are very far from random, and guarantee you 20 bits of entropy, rarely more. The next step that you need to take is to use the mechanism like "Password-Based KDF" it will make computationally difficult to distinguish data from random.

Use window.crypto.getRandomValues, like this:

var random_num = new Uint8Array(2048 / 8); // 2048 = number length in bits
window.crypto.getRandomValues(random_num);

This is supported in all modern browsers and uses the operating system's random generator (e.g. /dev/urandom). If you need IE11 compatibility, you have to use their prefixed implementation viavar crypto = window.crypto || window.msCrypto; crypto.getRandomValues(..) though.

Note that the window.crypto API can also generate keys outright, which may be the better option.

Crypto-strong

to get cryptographic strong number from range [0, 1) (similar to Math.random()) use crypto:

let random = ()=> crypto.getRandomValues(new Uint32Array(1))[0]/2**32;


console.log( random() );

Many years ago, you had to implement your own random number generator and seed it with entropy collected by mouse movement and timing information. This was the Phlogiston Era of JavaScript cryptography. These days we have window.crypto to work with.

If you need a random integer, random-number-csprng is a great choice. It securely generates a series of random bytes and then converts it into an unbiased random integer.

const randomInt = require("random-number-csprng");
(async function() {
let random = randomInt(10, 30);
console.log(`Your random number: ${random}`);
})();

If you need a random floating point number, you'll need to do a little more work. Generally, though, secure randomness is an integer problem, not a floating point problem.

If you need large amounts, here's what I would do:

// Max value of random number length
const randLen = 16384
var randomId = randLen
var randomArray = new Uint32Array(randLen)


function random32() {
if (randomId === randLen) {
randomId = 0
return crypto.getRandomValues(randomArray)[randomId++] * 2.3283064365386963e-10
}
return randomArray[randomId++] * 2.3283064365386963e-10
}


function random64() {
if (randomId === randLen || randomId === randLen - 1) {
randomId = 0
crypto.getRandomValues(randomArray)
}
return randomArray[randomId++] * 2.3283064365386963e-10 + randomArray[randomId++] * 5.421010862427522e-20
}


console.log(random32())
console.log(random64())

I know i'm late to the party, but if you don't want to deal with the math of getting a cryptographically secure random value, i recommend using rando.js. it's a super small 2kb library that'll give you a decimal, pick something from an array, or whatever else you want- all cryptographically secure.

It's on npm too.

Here's a sample I copied from the GitHub, but it does more than this if you want to go there and read about it more.

console.log(rando());                       //a floating-point number between 0 and 1 (could be exactly 0, but never exactly 1)
console.log(rando(5));                      //an integer between 0 and 5 (could be 0 or 5)
console.log(rando(5, 10));                  //a random integer between 5 and 10 (could be 5 or 10)
console.log(rando(5, "float"));             //a floating-point number between 0 and 5 (could be exactly 0, but never exactly 5)
console.log(rando(5, 10, "float"));         //a floating-point number between 5 and 10 (could be exactly 5, but never exactly 10)
console.log(rando(true, false));            //either true or false
console.log(rando(["a", "b"]));             //{index:..., value:...} object representing a value of the provided array OR false if array is empty
console.log(rando({a: 1, b: 2}));           //{key:..., value:...} object representing a property of the provided object OR false if object has no properties
console.log(rando("Gee willikers!"));       //a character from the provided string OR false if the string is empty. Reoccurring characters will naturally form a more likely return value
console.log(rando(null));                   //ANY invalid arguments return false
<script src="https://randojs.com/2.0.0.js"></script>