robhogan/react-native-securerandom

Export shim for getRandomValues if not defined

ricmoo opened this issue · 4 comments

Heya!

I was wondering if you would consider adding a shim for crypto.getRandomValues if it isn't defined. I've found a lot of libraries out there that shim it, but all in a very insecure way, and someone pointed me to your library, which does things correctly.

I think all you would need to add to the bottom of your index.js is:

if (window.crypto == null) { window.crypto = {}; }
if (window.crypto.getRandomValues == null) {
    window.crypto.getRandomValues = function(array) {
        const random = generateSecureRandom(array.length);
        for (let i = 0; i < array.length; i++) { array[i] = random[i]; }
    };
}

I will already be linking this library into the docs for ethers, with the snippet above to get secure random number generation, but if you included the above snippet in your code, it would just magically exist and a simple npm install would be enough to make it work without additionally files needed by consumers.

Thanks for your consideration. :)

I'm afraid this isn't possible - generateSecureRandom requires going over the JS-native bridge and back and as such it's an asynchronous API - note that it returns a Promise. Crypto.getRandomValues is expected to synchronously populate an array.

Note anyway that getRandomValues isn't typically implemented with a continuous source of strong randomness, it's only seeded with "enough" entropy (and the spec does not mandate any minimum entropy). That's often good enough, but you should use it with that in mind for cryptographic use cases, if you use it at all.

If you wanted to polyfill it, you could use this library to (asynchronously) generate a secure seed and from then on use a synchronous pure JS CSPRNG like isaac. That would be spec compliant, and no less secure than you'd necessarily get from a browser.

Thanks, you are absolutely correct. Now that I see you return a promise.

My concern with the method that use a random seed is that the seed is available to anything working within that process, so any malicious library or script that is able to run can sniff that seed out and replay random sequences.

The other libraries I’ve found do it this way by exposing a constant seed from the RN side to JS, which is available to anything...

Thanks for your feedback though. I’ll close this issue as it doesn’t make sense the way I thought. :)

Just out of curiosity, when you say "available to anything", what kind of threat are you concerned about? Afaik, the memory that holds the seed is only readable by your app and the OS. I'm not aware of any additional risk there.

If you have potentially malicious code within your app bundle or binary, you're pretty much doomed anyway. For example, it's trivial to patch window.crypto.getRandomBytes to either manipulate what it returns or record everything it returns (although in any given l app there are almost certainly much more direct ways to cause trouble!).

So, potentially malicious code is certainly one concern. It's true if they have that level of access, there are likely far more nefarious things they could do, but it is still a vector that is best removed. Using Object.defineProperty you could put some level of safeguard against overwriting window.crypto, but there are still issues, sure.

There is also the potential for accidentally exposing this seed, or due to the way some bundlers handle "links" and multiple versions, that there could be two copies of the JS portion of the library included in the bundled output. In this case, you could have two completely isolated parts of your program running the same sequence of random values, without them realizing it. If one is being used for private keys and the other for public UUIDs, then the UUIDs could be leaking private keys.

These are obviously fairly contrived situations, but the solution of letting something be public that is required to be secret to safeguard cryptographic primitives, seems like it could have unintended consequences at best. :)

Out of curiosity, does the React Native bindings not offer the ability to create a way to synchronously get this data? I haven't dug much into it, but I'm surprised no one has just exposed it directly, since SecRandomCopyBytes is itself synchronous... But no library I've seen offers this.