Closed max-mapper closed 5 years ago
I created a small library to emulate the node crypto API (only randomBytes()
and createHash('sha1')
). It uses the new expo-random
.
It's very raw, but it got node-rsa
to work without ejecting. Maybe useful for this one too...
I was able to solve this using a newer method that relies on the new "extraNodeModules" feature in RN:
npm install --save-dev react-native-randombytes
exports
: resolver: {
extraNodeModules: {
crypto: path.resolve(__dirname, 'extra_modules/crypto')
},
},
import { randomBytes } from 'react-native-randombytes';
exports.getRandomValues = function getRandomValues(arr) {
let orig = arr;
if (arr.byteLength !== arr.length) {
// Get access to the underlying raw bytes
arr = new Uint8Array(arr.buffer);
}
const bytes = randomBytes(arr.length);
for (var i = 0; i < bytes.length; i++) {
arr[i] = bytes[i];
}
return orig;
};
exports.randomBytes = exports.rng = exports.pseudoRandomBytes = exports.prng = randomBytes;
global.crypto = require('crypto');
Everything works fine after that, but I'm getting pretty bad performance on a release build on an iPhone X. Argon2id using only 6mb and 1 iteration takes about 8 seconds. @maxogden do you recall any specific benchmarks in your RN testing?
@mobitar, they say in the libsodium docs that argon performance is bad in JS. Take a look at https://download.libsodium.org/doc/password_hashing and search for "JavaScript".
With that being sad, I haven't tried it myself.
Although testing the same code on a browser with 65mb and 4 iterations takes < 1s.
There is no JIT in JavaScript on iOS, so performance of math heavy stuff is pretty bad there. It's the same with RSA (especially keygen) and other math heavy pieces of code. Your best bet is to move your crypto code to a native module.
Yeah, probably what I'll do..
So far found:
Please let me know what you end up using. I think I may just end up using swift-sodium and have my own interfaces to javascript. Not sure to be honest.
Just gave this a try and it's blazing fast: https://github.com/lyubo/react-native-sodium
67mb, 5 iterations took 400ms on my iPhone X. Same on browser takes 900ms.
For our app we ended up doing a custom build of libsodium.js that has wasm as a separate JS bundle from asm.js. Then we ended up using the asm.js only version on RN and the wasm only one on Electron. For our use case it didn't make sense to bundle both and include the fallback mechanism. We are happy with the asm.js bundles performance on RN. It would be nice if there were official separate builds, if there's interest I can look into sending a PR.
We are happy with the asm.js bundles performance on RN
I'm assuming you're not using argon2 then? Probably just the encryption algos?
For posterity: I made some benchmarks locally yesterday with the default libsodium pwhash algo, and both opslimit and memlimit set to the "moderate" constant.
For comparison, scrypt, even without JIT, is not as painfully slow.
I finally got it to work with no (major) hacks, no rn-nodeify and no editing of the metro config!
react-native-get-random-values
"crypto": "npm:leftpad",
"fs": "^0.0.1-security",
"path": "^0.12.7",
This is needed because metro tries to resolve modules no matter if they are actually used at runtime, so let's just have it load something. Whatever it is.
document
global (because libsodium tries to access it): global.document = global.document || {};
So wherever you are import libsodium from (or e.g. at the top of your index.js) should look like:
import 'react-native-get-random-values';
global.document = global.document || {};
import _sodium from 'libsodium-wrappers';
That's it.
Hmm, revisiting this, neither my nor your instructions seem to work anymore. I get:
Error: Both wasm and asm failed to load TypeError: undefined is not an object (evaluating 'I.asm.rc')
It only works if you enable Chrome debugging. Once you turn off the debugger, it no longer works.
@mobitar, here are the instructions I give to my users (other devs): https://docs.etebase.com/installation#on-react-native though as you can see I packaged it in a library that does other things too. To create the library I just followed the instructions I wrote in my previous comment.
Wait you actually posted that you had the same exact issue: https://github.com/jedisct1/libsodium.js/issues/249
Sorry, I completely forgot that I had to pin libsodium.js to a lower version due to #249. With the lower version, and the instructions above, it works as expected.
Hi, thank you for all the work on libsodium. I'm trying to load libsodium-wrappers.js and libsodium.js in React Native (android emulator). I random into the
r.useBackupModule is not a function
error using the current released versions, so I compiled master using emsdksdk-releases-fastcomp-3b8cff670e9233a6623563add831647e8689a86b-64bit
.I also had to make these edits in
libsodium-wrappers.js
:require('libsodium')
withrequire('./libsodium.js')
so it loads my dist versionrequire(
inlibsodium.js
withrequirexxx(
so the RN Metro Bundler doesn't try to bundle the requires (likerequire('path')
, which won't work in RN unless you insert a path shim).At this point I got the error:
Both wasm and asm failed to loadNo secure random number generator found
. I was able to fix this by following these steps https://www.npmjs.com/package/react-native-crypto#install and changing backrequirexxx('crypto')
torequire('crypto')
.Now I have it all loading and working. Just wanted to share in case anyone else was trying to load the library in React Native, as I couldn't find much info online.