I'm trying to generate keypairs in a React Native project. The key pair generation tool relies on the crypto
module's random byte generation, which produces a buffer of a specified lengths with random byte values.
In order to use the crypto
module within React Native, it has to be browserified, and the browserified random number generator looks like this:
https://github.com/crypto-browserify/randombytes/blob/master/browser.js
Here's the key component:
var crypto = global.crypto || global.msCrypto
if (crypto && crypto.getRandomValues) {
module.exports = randomBytes
} else {
module.exports = oldBrowser
}
Indeed, when debugging the application with Chrome, everything works fine, but when running it on iOS's JavaScriptCore engine, the oldBrowser
method gets called instead, throwing the following error:
secure random number generation not supported by this browser use chrome, FireFox or Internet Explorer 11
Thus I'm trying to find a replacement for the random bytes generation. One module I found is this one:
https://www.npmjs.com/package/react-native-randombytes
It uses the device's native libraries to generate a random number, and exposes it to React Native through their Obj-C/JS interface. It should be noted that this method only works on iOS, and the library's author doesn't have an Android solution yet, but that's an issue for another time.
This method works, in that it can generate random bytes, but it has one major drawback. React only supports asynchronous interfacing between Objective-C and JavaScript, which means that this method returns its results asynchronously. The original randomBytes
method is synchronous, and pretty much every SDK out there that relies on it uses it synchronously. So if we were to go with the async version, all the SDKs would have to be rewritten for it, including all dependencies that rely on methods that used to be synchronous and now would no longer be.
Thus I'm trying to find a way to make the asynchronous native random number generator work synchronously. There are several node packages that do that, the most prominent one of them being deasync
, but deasync
relies on some core Node modules that cannot be browserified, so the synchronous version doesn't work.
Alternatively, I've tried wrapping it in a method that would set a semaphore, call the async generator, and wait in a while loop for the semaphore's value to change. That attempt failed because the while loop was blocking the callback from ever executing. Here's an approximation of my attempts, where the call to the async method has been replaced with a setTimeout
, and the random number to be returned is a four, as determined by a fair dice roll.
function testSynchronicity() {
var isDone = false;
setTimeout(function() {
isDone = true;
}, 1000); // set isDone to true after a second
while (!isDone) {
// do nothing
}
return 4;
};
As this wasn't working, I figured I would try a completely different random number generator entirely, without the native-code-relying react-native-randombytes
module and went with this one for JavaScript:
https://github.com/skeeto/rng-js
It worked fine within Node itself, but after browserifying it and trying to run the first example within React Native, it threw an error saying that the main object was not a constructor. Here's what the example looks like:
var RNG = require('./rng_react'); // rng_react is rng-js browserified
var rng = new RNG();
var randomValue = rng.random(0, 255, false);
So at this point, I'm at a bit of a loss, and would appreciate any help. Thanks!
EDIT: If all else fails, there's this, but I think it would pretty much beat the purpose of the question. https://github.com/bitpay/bitcore-lib/blob/master/lib/crypto/random.js#L37
Your solution does answer the question but seems a bit complex. In particular, why not use only SJCL?
In my case I've ended up using react-native-securerandom, which is just a thin wrapper over Android and iOS native calls. Then I've done this to initialise SJCL's RNG:
const { generateSecureRandom } = require('react-native-securerandom');
const sjcl = require('lib/vendor/sjcl');
const randomBytes = await generateSecureRandom(1024/8);
let temp = [];
for (let n in randomBytes) {
if (!randomBytes.hasOwnProperty(n)) continue;
temp.push(randomBytes[n].toString(16));
}
const hexSeed = sjcl.codec.hex.toBits(temp.join(''));
sjcl.random.addEntropy(hexSeed, 1024, 'generateSecureRandom');
I have found an answer that usually works. However, it is imperfect, because it works only if the randomBytes
method is not required during app launch.
My solution does involve using the react-native-randombytes
library. It relies on iOS's built-in CSPRNG to generate a random buffer, and then returns it asynchronously. In order to support synchronous responses, I expanded the moduel's randomBytes
to not throw an error when no callback method is provided, but rather to use Stanford's JavaScript Crypto Library to generate random "words," as they're called, convert those to a buffer and then trim it accordingly:
var sjcl = require('sjcl');
var sjclRandom = new sjcl.prng(10);
var RNRandomBytes = require('react-native').NativeModules.RNRandomBytes;
module.exports.randomBytes = function(length, cb) {
if (!cb) {
var size = length;
var wordCount = Math.ceil(size * 0.25);
var randomBytes = sjclRandom.randomWords(wordCount, 10);
var hexString = sjcl.codec.hex.fromBits(randomBytes);
hexString = hexString.substr(0, size * 2);
return new Buffer(hexString, 'hex');
}
RNRandomBytes.randomBytes(length, function(err, base64String) {
if (err) {
cb(err);
} else {
cb(null, new Buffer(base64String, 'base64'));
}
});
};
The crux is, in order for the SJCL library to have sufficient entropy, it needs to have been seeded properly. So, on startup, we use the asynchronous CSPRNG functionality to seed the SJCL random number generator:
module.exports.randomBytes(4096, function(err, buffer) {
var hexString = buffer.toString('hex');
// we need to convert the hex string to bytes, or else SJCL assumes low entropy
var stanfordSeed = sjcl.codec.hex.toBits(hexString);
sjclRandom.addEntropy(stanfordSeed, 10, 'csprng');
});
Thus, we have a synchronous randomBytes
method within React Native, provided we have had the opportunity to call it asynchronously at least once before we need its synchronous functionality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With