React-native: JS code protection

Created on 1 May 2015  Â·  40Comments  Â·  Source: facebook/react-native

Hi everyone,

Is there any plan to encrypt the javascript code in production mode (release for AppStore)?
I know that right now it can be obfuscated, but this it's not enough :)

Thank you.

Locked

Most helpful comment

As @vjeux said, we have no immediate plans to add encryption for JS bundle files, and yes, under the currently recommended bundling instructions, your JS will be included as plaintext that can be extracted and de-obfuscated with relative ease.

If you are just interested in preventing casual inspection of the JS source (as opposed to stopping a determined attacker) then I believe there is another way to bundle your code that will be much harder to crack (I've not tested this, so you'll have to try it and let me know if there are any problems):

1) Compile your jsbundle file as normal
2) Base64 encode the jsbundle file, then copy the contents:
3) In your AppDelegate (or wherever you set up your RCTBridge/RCTRootView), add the following:

jsCodeLocation = [NSURL URLWithString:@"data:text/javascript;base64,AAAAAAAAAAAAA"];

Where AAAAAAAAAAAAA is your base64-encoded bundle. Then pass that URL into RCTRootView as normal to load your app.

The advantage of embedding the source code in this way is that by including it as a string constant in your app (as opposed to a text file in the app resources) it will automatically be encrypted using Apple's FairPlay DRM when you upload it to the App Store.

That means that anyone who downloads the app from the store and looks in the resources won't find the code, but more importantly _even if they open the app binary in a hex editor_ they won't be able to find this base64 string and decode it because it will have been converted into encrypted gibberish. That makes it about as secure as ordinary native app code.

A determined attacker could still open the app on a jailbroken device and use some tools to copy the decrypted binary out of RAM, but as I said earlier, there's really no practical defence against that. This approach will certainly push the difficulty out of the realm of "casual curiosity" though.

Let me know if this works for you (or doesn't). It should be relatively easy to modify the packager to automate this if there's significant interest in doing it.

All 40 comments

You could do this by using an encryption library to encode the JS and decode it at runtime before passing it to the bridge. The problem is that you'd have to ship the decryption key with the app code, so it would be trivial for a hacker to decrypt it if they were inclined to do so.

I'd advise against including secret information in your application, even in native code. Code on the client is not secret - secrets should be kept on the server.

So what's the answer here? (omitting your personal beliefs and opinions)

Does the React project has any plans of protecting the developer's code or not?

@nicklockwood Thanks for the reply.

A determined hacker would get access to everything indeed, I'm talking more about "curious" people that might look into your house if you leave the door open ;)

Also the are plenty of cases where you would really need to store some stuff on the device starting with complete offline apps to apps that communicate with external servers which you don't control.
I know what ideally should be done, but in the real world things don't happen like this :) (Don't get me wrong please)

We're unlikely going to provide encryption or more sophisticated obfuscation.

Right now, all the module names leak and we're thinking of replacing them with numbers for efficiency reason, which should help a bit if we end up doing it.

But even with a more efficient code in place, the JS will still be in clear. Or am I getting it wrong?

That's correct, it probably won't fit your usecase.

We (as in Facebook) are not going to implement what you want anytime soon. That said, if you make the necessary changes inside of the codebase to have a pluggable way to inject an encryption/obfuscation plugin, that's something we may consider pulling in

@vjeux clear now, thank you :smile:

As @vjeux said, we have no immediate plans to add encryption for JS bundle files, and yes, under the currently recommended bundling instructions, your JS will be included as plaintext that can be extracted and de-obfuscated with relative ease.

If you are just interested in preventing casual inspection of the JS source (as opposed to stopping a determined attacker) then I believe there is another way to bundle your code that will be much harder to crack (I've not tested this, so you'll have to try it and let me know if there are any problems):

1) Compile your jsbundle file as normal
2) Base64 encode the jsbundle file, then copy the contents:
3) In your AppDelegate (or wherever you set up your RCTBridge/RCTRootView), add the following:

jsCodeLocation = [NSURL URLWithString:@"data:text/javascript;base64,AAAAAAAAAAAAA"];

Where AAAAAAAAAAAAA is your base64-encoded bundle. Then pass that URL into RCTRootView as normal to load your app.

The advantage of embedding the source code in this way is that by including it as a string constant in your app (as opposed to a text file in the app resources) it will automatically be encrypted using Apple's FairPlay DRM when you upload it to the App Store.

That means that anyone who downloads the app from the store and looks in the resources won't find the code, but more importantly _even if they open the app binary in a hex editor_ they won't be able to find this base64 string and decode it because it will have been converted into encrypted gibberish. That makes it about as secure as ordinary native app code.

A determined attacker could still open the app on a jailbroken device and use some tools to copy the decrypted binary out of RAM, but as I said earlier, there's really no practical defence against that. This approach will certainly push the difficulty out of the realm of "casual curiosity" though.

Let me know if this works for you (or doesn't). It should be relatively easy to modify the packager to automate this if there's significant interest in doing it.

@nicklockwood this should be good enough for a start, I'll run some tests and let you know. Thanks a bunch!

You may want to test that the string in the binary is truly encrypted. I can't point to any mach-file docs (mobile atm), but in my experience large clobs are concatendated to the binary and are completely unencrypted. To test this download the App Store version of your app and run strings on the binary.

For the record, thousands of Cordova apps are shipped to the app stores with all resources unencrypted.

Just saying...

JG

@moduscreate

:: sent from my mobile device ::

On May 1, 2015, at 21:25, Shayne Sweeney [email protected] wrote:

You may want to test that the string in the binary is truly encrypted. I can't point to any mach-file docs (mobile atm), but in my experience large clobs are concatendated to the binary and are completely unencrypted. To test this download the App Store version of your app and run strings on the binary.

—
Reply to this email directly or view it on GitHub.

@shayne thank you. I'll keep this in mind.

@jaygarcia For many apps this might be a "nice to have" however for most of my projects the code protection is required :)

Chiming in here as I've done quite a bit of work around license key protection and sorts. Please realise no matter how much effort you put in to "securing" your JS code or secrets in an iOS client app a determined attacker will always get what they want. From what I've read from other developers it's about a 10:1 effort. That is for every 10 hours you spend "protecting" your stuff it takes an attacker approx 1 hour to reverse your efforts.

Trying to keep the source private is pretty moot-point since it's very easy to monitor some of the Objective-C code that react-native uses.

The best thing I recommend to anyone is to ensure your jsbundle has a signature and you verify that signature before you let it be loaded into the bridge. This is especially important for anyone delivering updates over HTTP/HTTPS.

If you are heavily worried about code protection use heavy obfuscation and RSA public/private keys to encrypt the source. Again anyone with a jailbroken device and a bit of time can easily latch LLDB onto the RCTBridge methods and snag your code.

Thanks Robert!!

JG

:: sent from my mobile device ::

On May 3, 2015, at 05:05, Robert Payne [email protected] wrote:

Chiming in here as I've done quite a bit of work around license key protection and sorts. Please realise no matter how much effort you put in to "securing" your JS code or secrets in an iOS client app a determined attacker will always get what they want. From what I've read from other developers it's about a 10:1 effort. That is for every 10 hours you spend "protecting" your stuff it takes an attacker approx 1 hour to reverse your efforts.

Trying to keep the source private is pretty moot-point since it's very easy to monitor some of the Objective-C code that react-native uses.

The best thing I recommend to anyone is to ensure your jsbundle has a signature and you verify that signature before you let it be loaded into the bridge. This is especially important for anyone delivering updates over HTTP/HTTPS.

If you are heavily worried about code protection use heavy obfuscation and RSA public/private keys to encrypt the source. Again anyone with a jailbroken device and a bit of time can easily latch LLDB onto the RCTBridge methods and snag your code.

—
Reply to this email directly or view it on GitHub.

@rborn did you get a chance to try my suggestion? Any problems?

Sorry, I'm traveling this days and I'll be able to touch the code only the next week.
But as soon as I'm back I'll test it and let you know :)

Sent from my iPhone

On May 6, 2015, at 12:37 AM, Nick Lockwood [email protected] wrote:

@rborn did you get a chance to try my suggestion? Any problems?

―
Reply to this email directly or view it on GitHub.

I'm really sorry for the delay.

@nicklockwood I did what you suggested (base64 the jsbundle) and the test app works ok (deployed both on sim and device).

However after I generated the ipa for appstore a strings command shows the base64 code in clear (as @shayne indicated).

Ah, pity.

The next option would be to encrypt the base64 URL and then decrypt it before passing to the bridge then. Not very much harder, but still an extra step. Doesn't really matter if the string is stored in the app or in a file.

Wait, when you say generated ipa for app store, do you mean you submitted it to Apple and then downloaded in iTunes once it was approved and released, or did you just export a release build from xcode?

The encryption is applied by Apple in the cloud, so it doesn't encrypt the ipa before you actually submit it.

Oh, didn't know this, I thought the encryption is done locally.
But if it's like this, why the jsbundle files are not encrypted? (found that in a react-native app that's in the Appstore).

Fairplay encryption is only applied to certain parts of the app binary itself, not to assets in the resources folder (they unzip your ipa, add the encryption, and then reassemble the ipa)

I see, thank you

Sadly I cannot test right now with an app in appstore :disappointed:

Very interesting discussion, good to see some options presented here. Given that this is outside of the scope of React Native itself, let's move the discussion to discuss.react.js.org if there's anything left to be said!

@rborn - feel free to ping us again in this discussion with followup if you do get a chance to test it in the app store!

Is there a recommended tool to use for obfuscation of react native javascript code? Can I use something like Uglify?

I tried the Base64 encoding of jsbundle file in iOS, and it works great.
But having problems on android.
How do the same on android jsbundle.

@sudarshanJagtap Hi, maybe I know how to do the Base64 encoding of jsbundle file in iOS ?

Can their be some directions to the community as to how to implement obfuscation or alternative security measures in the js bundle?

The JavaScript is already obfuscated with the minifier. The security (not sure if that's the right word...) is comparable to a website's. RN's packager already ships with that.

+1 to automate the jsCodeLocation trick. Is there anything similar for Android?

@rborn @brentvatne
Hey guys!
Can you please tell how to do "Base64 jsCodeLocation" in details? Or how you did it?
I'm using RN 0.40 and have not enough knowledge in C for now to do it myself.

@sudarshanJagtap @jesucarr Hey guys! How you did base64 "in details"?

I have implemented embedding of jsbundle in #12587

For someone trying to figure it out. Here are the steps:

  1. Get app bundle from localhost and save it as index.ios.bundle
    http://localhost:8081/index.ios.bundle?platform=ios&dev=false
  2. Encode file (in terminal)
    > openssl enc -base64 -A -in index.ios.bundle -out main64-1.jsbundle
  3. (Optional)
    Decode for testing (in terminal)
    > openssl enc -base64 -d -A -in main64-1.jsbundle -out main64-1-dec.jsbundle
  4. (Once)
    Updated files on ios react library based on this pull request (this can be found in your xcode project). https://github.com/facebook/react-native/pull/12587
  5. Make JSBundle.h and paste encoded data in it
    #define JSBUNDLE_BASE64 @"[PASTE ENCODED DATA HERE]"
  6. Update app AppDelegate.m
    #import "JSBundle.h"
    jsCodeLocation = [NSURL URLWithString:@"data:text/javascript;base64," JSBUNDLE_BASE64];

I'm having problems with static resources. After using this process, all my images are gone from release app.
I am not sure where to put the "assets" directory so that react-native can find them.

If I understand it correctly, RN looks for the "assets" directory in the same folder as "main.jsbundle". But now I don't have main.jsbundle file.

Yeah same problem here. Also the the loading of the js code is very slow now. Used to take 3 seconds, now its over 10s.

@chatras Are you testing this in a prod build?

How should we use getJSBundleFile in Android in order to load and decode the base64 version?

In Android it doesn't make any sense as strings are not encrypted upon binary download. Additionally there were problems with loading resources and performance with base64 approach.
After all even Apple's strings encryption is fairly easy to crack with jailbroken device. So it is better to obfuscate code, it is proven way to make it harder (not impossible) to read.

Hi @chatras , @nicklockwood ,

I am trying to use this code but its not working.
jsCodeLocation = [NSURL URLWithString:@"data:text/javascript;base64," JSBUNDLE_BASE64];

I don't think. its decode the JSBUNDLE_BASE64. can you please explain meaning of this line.

@nitinsoni9235 first thing first, don't do it. It's a false sense of security. More details here https://github.com/facebook/react-native/pull/12587
Second it will slow things down, increase memory usage and will break loading if images from resources.
If you really-really want to give it a shot, try code form this PR https://github.com/facebook/react-native/pull/12587

Was this page helpful?
0 / 5 - 0 ratings