BigInteger.js
BigInteger.js copied to clipboard
Cryptographically Secure RNG
This is more of a recommendation than an issue, but the bigInt.randBetween()
function could be enhanced with a cryptographically secure alternative. Most of the content can be copied, but instances of Math.random()
can be replaced with a function that returns window.crypto.getRandomValues(new Uint32Array(1))[0]/4294967295
. For performance conditions, the original should be kept, but this new option would definitely be useful.
I'll have to think about this some. In principle I like the idea, but there are a few kinks to consider:
- We'll have to fall back to
Math.random
for browsers that don't supportcrypto
(although nowadays that is relatively a fairly small percentage of users) - BigInteger.js is used for both browser and
node.js
platforms. In node.js there is also a built-incrypto.randomBytes
method with an API slightly different fromwindow.crypto.getRandomValues
. We would have to detect which methods are supported in the environment that BigInteger is run in. - As you mentioned, performance is something to consider, although I'm unaware of anybody using bigInt.randBetween for tight-performance applications. Anyway, we'd have to do some profiling to see what the consequences would be.
Thanks for your consideration on this topic. I recognized by your earlier comments on past issues that you are hesitant in gearing your library towards cryptography, but I can assure that it does assume a healthy portion of you audience. I will try to do some further research on this. I must admit I oversaw cross-compatibility when I proposed my solution, as it was a function I personally added to the library when developing my own RSA program for Chrome. Two questions that might lead to solutions:
- Can your library detect the platform its running on (node vs browser) and address them differently? (Complicates library, user friendly)
- Could the
bigInt.randBetween()
function have an optional argument to pass in that could personally define the function used as a RNG? (Easy fix, but requires more work from user)
I'm inclined to implement this since this is not the first time this has been requested.
This will be straightforward if I have a function random(max)
that returns a random number in the range [0, max)
, with max
being at most 9007199254740992
. With Math.random()
the best we can do is
Math.floor(Math.random() * max)
With window.crypto.getRandomValues
we could try
Math.floor(window.crypto.getRandomValues(new Uint32Array(1))[0] * max / 4294967295)
This is a quick-and-dirty way that kind of works, but it will not generate a uniform distribution, and will skip some values if max
is significantly larger than 4294967295
.
The same thing is true for crypto.randomBytes
in node.js.
If you could look into an elegant way to do this with window.crypto.getRandomValues
and crypto.randomBytes
, that would be helpful.
I have found a method to generate a random number up to any specified number of bytes, which could be used as a more accurate multiplier that you were mentioning above:
function random(num_bytes) { var random_array = window.crypto.getRandomValues(new Uint32Array(num_bytes)); var big_random = ""; for(var i = 0; i < num_bytes; i++) { var small_random = bigInt(random_array[i]).toString(2); while(small_random.length < 32) { small_random = "0" + small_random; } big_random += small_random; } return bigInt(big_random, 2); }
If we use a number of bytes, it only works for the situation when the size of the range of numbers we want is a power of 256. How do we find a random number between 0 and 1000, for example?
So first we identify an upper and lower bound to compute a range. Then we multiply the range by x amount of 32-bit bytes and divide by 2 to the power of the number of bits minus one. Finally, add the lower bound. So, using the function random()
defined previously:
function crypto_randomBetween(a, b) {
var min = bigInt.min(a, b), max = bigInt.max(a, b);
var range = max.subtract(min);
var num_bytes;
return min.add(range.multiply(random(num_bytes)).divide(bigInt[2].pow(num_bytes * 32).prev()));
}
Obviously, the number of bytes used for the multiplier must depend on the magnitude of the range, but I have yet to finalize that math.
Note I did make a small edit to the random bytes generator above as leading zeros were being lost in the process of decimal to binary conversion.
I have done a bit of work and would like to submit a final RNG for you to test and approve. I now believe it to be as random as the original constructive function, Math.crypto.getRandomValues()
, but I am not sure of a better method to submit a larger block of code. Where could I do this?
If you're wanting to put it directly in the library you can make a pull request. If you just want to share some code with me you can make a gist and I'll look at it
Thank you, I am relatively new to GitHub and am still learning all its processes. Here is my RNG: https://gist.github.com/benjaminBrownlee/2823e06d0b455969a06a13eddbadeb48 It appears to be working, but I imagine you have the skill/resources to better test its accuracy and distribution.
OK, thanks, I'll try to look at it later
Clicking that link appears to be broken, here's the intended target: https://gist.github.com/benjaminBrownlee/2823e06d0b455969a06a13eddbadeb48
Thanks for fixing the URL. As I said, I am still getting used to GitHub.
Sorry I still haven't tried adding this to the library, I've been rather busy lately
Is anyone aware of where to find tools or processes to check mass calculations? I want to run my proposed RNG at numerous bit sizes and collect the data to check for accuracy and distribution.
How about injecting the random method instead of having so much headaches about it?
@benjaminBrownlee you can format your code blocks by using triple backticks, like this: ```js //JavaScript code here ```
Adding the language name (I used the short version "js" but it also works with "javascript") activates syntax highlighting for that lang (if supported)