react-native-obfuscating-transformer: Code not being encrypted

I have spent a good while playing around with multiple options and examples and can’t seem to get it to work. My setup is the following:

"metro-react-native-babel-preset": "0.56.0",
"metro-react-native-babel-transformer": "^0.59.0",
 "react-native-obfuscating-transformer": "^1.0.0",
"react-native": "^0.61.0",

metro.config.js

/**
 * Metro configuration for React Native
 * https://github.com/facebook/react-native
 *
 * @format
 */

module.exports = {
  transformer: {
    babelTransformerPath: require.resolve("./transformer"),
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
  },
};

transformer.js

const obfuscatingTransformer = require("react-native-obfuscating-transformer")

const filter = filename => { 
  return filename.startsWith("src");
};

module.exports = obfuscatingTransformer({
// this configuration is based on https://github.com/javascript-obfuscator/javascript-obfuscator
  obfuscatorOptions:{
    compact: true,
    controlFlowFlattening: false,
    deadCodeInjection: false,
    debugProtection: false,
    debugProtectionInterval: false,
    disableConsoleOutput: true,
    identifierNamesGenerator: 'hexadecimal',
    log: false,
    renameGlobals: false,
    rotateStringArray: true,
    selfDefending: true,
    shuffleStringArray: true,
    splitStrings: false,
    stringArray: true,
    stringArrayEncoding: false,
    stringArrayThreshold: 0.75,
    unicodeEscapeSequence: false
  },
  upstreamTransformer: require('metro-react-native-babel-transformer'),
  emitObfuscatedFiles: false,
  enableInDevelopment: true,
  filter: filter,
  trace: true
})

The command I am using to build the output js bundle for testing is the following:

npx react-native bundle --entry-file=index.js --bundle-output='./bundle.js' --dev=false --platform='ios' --assets-dest='./ios' --reset-cache

I can see the output from stdout of the transformer logging which files are being obfuscated, however when I look at the entire bundle and even the sub obfuscated files, the variable names and function calls are not encrypted.

Below is an example of a before and after obfuscation:

Before (screens.js):

import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';

import firebase from 'react-native-firebase';
const crashlytics = firebase.crashlytics();

import configureStore from 'app/store';

// Initialise global store
export const store = configureStore()

export function registerScreens() {

  crashlytics.enableCrashlyticsCollection();
  crashlytics.log('[utils][screens] - Registering screens...');

  // Register screens with redux stores
  Navigation.registerComponentWithRedux('Initialising', () => require('screens/Initialising').default, Provider, store);
  Navigation.registerComponentWithRedux('Auth', () => require('screens/Auth').default, Provider, store);
  Navigation.registerComponentWithRedux('VerifyPhoneNumber', () => require('screens/VerifyPhoneNumber').default, Provider, store);
  Navigation.registerComponentWithRedux('SignIn', () => require('screens/SignIn').default, Provider, store);

  // Profile Stack
  Navigation.registerComponentWithRedux('Profile', () => require('screens/Profile').default, Provider, store);
  Navigation.registerComponentWithRedux('QRScanner', () => require('screens/Profile/QRScanner').default, Provider, store);

  // Register Stack
  Navigation.registerComponentWithRedux('InitialRegister', () => require('screens/Register/InitialRegister').default, Provider, store);
  Navigation.registerComponentWithRedux('RegisterDetails', () => require('screens/Register/RegisterDetails').default, Provider, store);

  // Rewards Stack
  Navigation.registerComponentWithRedux('Rewards', () => require('screens/Rewards').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardBack', () => require('screens/Rewards/RewardBack').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardRedeemed', () => require('screens/Rewards/RewardRedeemed').default, Provider, store);

  // Home stack
  Navigation.registerComponentWithRedux('Home', () => require('screens/Home').default, Provider, store);

  // Marketplace stack
  Navigation.registerComponentWithRedux('MarketplaceBack', () => require('screens/Rewards/MarketplaceBack').default, Provider, store);

  // Bundles
  Navigation.registerComponentWithRedux('PreBundle', () => require('screens/Home/PreBundle').default, Provider, store);
  Navigation.registerComponentWithRedux('BundleQuestion', () => require('screens/Home/BundleQuestion').default, Provider, store);
  Navigation.registerComponentWithRedux('BundleComplete', () => require('screens/Home/BundleComplete').default, Provider, store);

  // Help Screens
  Navigation.registerComponentWithRedux('HomeHelp', () => require('screens/Help/HomeHelp').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardsHelp', () => require('screens/Help/RewardsHelp').default, Provider, store);

  // Modals
  Navigation.registerComponentWithRedux('VersionUpdateModal', () => require('misc/Modals/VersionUpdateModal').default, Provider, store);
  Navigation.registerComponentWithRedux('PolicyUpdateModal', () => require('misc/Modals/PolicyUpdateModal').default, Provider, store);
}

After (screens.obfuscated.js):

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports, "__esModule", { value: true });exports.registerScreens = registerScreens;exports.store = void 0;var _reactNativeNavigation = require("react-native-navigation");
var _reactRedux = require("react-redux");

var _reactNativeFirebase = _interopRequireDefault(require("react-native-firebase"));


var _store = _interopRequireDefault(require("../../store"));var crashlytics = _reactNativeFirebase.default.crashlytics();

// Initialise global store
var store = (0, _store.default)();exports.store = store;

function registerScreens() {

  crashlytics.enableCrashlyticsCollection();
  crashlytics.log('[utils][screens] - Registering screens...');

  // Register screens with redux stores
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Initialising', function () {return require("../components/screens/Initialising").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Auth', function () {return require("../components/screens/Auth").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('VerifyPhoneNumber', function () {return require("../components/screens/VerifyPhoneNumber").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('SignIn', function () {return require("../components/screens/SignIn").default;}, _reactRedux.Provider, store);

  // Profile Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Profile', function () {return require("../components/screens/Profile").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('QRScanner', function () {return require("../components/screens/Profile/QRScanner").default;}, _reactRedux.Provider, store);

  // Register Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('InitialRegister', function () {return require("../components/screens/Register/InitialRegister").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RegisterDetails', function () {return require("../components/screens/Register/RegisterDetails").default;}, _reactRedux.Provider, store);

  // Rewards Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Rewards', function () {return require("../components/screens/Rewards").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardBack', function () {return require("../components/screens/Rewards/RewardBack").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardRedeemed', function () {return require("../components/screens/Rewards/RewardRedeemed").default;}, _reactRedux.Provider, store);

  // Home stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Home', function () {return require("../components/screens/Home").default;}, _reactRedux.Provider, store);

  // Marketplace stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('MarketplaceBack', function () {return require("../components/screens/Rewards/MarketplaceBack").default;}, _reactRedux.Provider, store);

  // Bundles
  _reactNativeNavigation.Navigation.registerComponentWithRedux('PreBundle', function () {return require("../components/screens/Home/PreBundle").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('BundleQuestion', function () {return require("../components/screens/Home/BundleQuestion").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('BundleComplete', function () {return require("../components/screens/Home/BundleComplete").default;}, _reactRedux.Provider, store);

  // Help Screens
  _reactNativeNavigation.Navigation.registerComponentWithRedux('HomeHelp', function () {return require("../components/screens/Help/HomeHelp").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardsHelp', function () {return require("../components/screens/Help/RewardsHelp").default;}, _reactRedux.Provider, store);

  // Modals
  _reactNativeNavigation.Navigation.registerComponentWithRedux('VersionUpdateModal', function () {return require("../components/misc/Modals/VersionUpdateModal").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('PolicyUpdateModal', function () {return require("../components/misc/Modals/PolicyUpdateModal").default;}, _reactRedux.Provider, store);
}

About this issue

Most upvoted comments

No progress, my team had me do a POC with Jscrambler first, which I’ve done, and it works great, but for a heafty price-tag. The company I work for is now weighing the options of paying Jscrambler the annual subscription fee to handle obfuscation / anti-tampering, or giving me a work-item to fix what’s broken in this open source library. I wont know for another 2 week if they choose the latter, if they do I will let this thread know.

@dooleyb1 did you manage to resolve this? If not, did you use an alternative solution?

My files don’t appear to be obfuscated at all.

Original file:

const App: React.FC = () => {
  const myApiKey = 'foo';
  console.log(myApiKey);

Obfuscated file:

var App = function App() {
  var myApiKey = 'foo';
  console.log(myApiKey);

With the following packages

"metro-react-native-babel-preset": "^0.59.0",
"react-native-typescript-transformer": "^1.2.13",
"react-native-obfuscating-transformer": "^1.0.0",
"react-native": "0.63.3",

@hannigand No unfortunately I gave up attempting to implement obfuscation on my files and have not re-attempted since.

I solved this by modifying obfuscatingTransformer.js in node_modules\react-native-obfuscating-transformer\dist change

fs.writeFileSync(path.join(emitDir, filename), code);

to

fs.writeFileSync(path.join(emitDir, filename), obfuscateCode_1.obfuscateCode(code, obfuscatorOptions));

it seems the author forgot to modify this one.

@AustinZuniga thanks man that worked perfectly for the individual obfuscated files, however it still seems to have no effect on the overall bundle.js file. Any idea?

I noticed that one too. Try comparing bundle.js with and without using react-native-obfuscating-transformer. Use a JS code beautifier and diff file tool to see the difference. It seems react-native-obfuscating-transformer is working by obfuscating the code, but the identifier names not turning into hexadecimal or mangled algorithm. Maybe it has something to do with metro transformer

Yeah I can see from looking at the bundle.js that the code has definitely been changed and obfuscated a bit. Would just love for the identifier names to turn into the hexadecimal hash. Will leave this issue open and see if anyone can help. Thanks 👍