I recently wrote a mobile app to keep track of an inventory. The inventory has about 1,000 records and the app only reads the data. I chose to store the data in JSON format and host it from an AWS S3 bucket. This allows just downloading the file using a public S3 URL.

I don't care if the file is download by someone else, so I didn't need authentication, but want to keep the contents secret, so I encrypted it. The pass phrase is included in the program somewhere, but the user must enter a PIN to complete the encryption pass phrase. Because the file is obtained via https, this keeps the data secret until it's decrypted in the app. The file should not be stored unencrypted in the app.

I'm using the openssl command to encrypt the before uploading it to AWS. I'm using the FileSystem component in Expo to download the file into my React Native app. The encrypted file is then read, decrypted and parsed to create a JSON object. The app just displays the JSON in a long list.

Decrypting OpenSSL using JavaScript

We need an NPM module for React Native that an decrypt data that has been encrypted by openssl. The crypto-js NPM module is used in most NodeJS programs, but it does not work in React Native. A pure JavaScript implementation is needed. Fortunately, some nice folks have re-implemented crypto-js in pure JavaScript and published it as the crypto-es NPM module. This module does work in React Native, NodeJS and a browser.

openssl can encode data in binary or Base64. I found that it's easiest to download and decrypt files encoded in Base64 using a combination of the FileSystem component and crypto-es. Base64 encoding is often formatted in rows of 76 characters with a newline at the end. openssl can create one long string instead, this is the best format. The command line to encode the file is:

% openssl enc -aes-256-cbc -base64 -A < file.txt > encoded_file.txt

-base64 selects Base64 encoding with rows, -A makes the rows a long string.

I tried decrypting files without using -base64 and -A using crypto-es without success. I'm sure it's possible, but using this format worked best for me.

We can use this JavaScript file with NodeJS:

import fs from 'fs';
import CryptoES from 'crypto-es';

if (process.argv.length == 3) {
  const pass = process.argv[2].split(':')[1];
  const data = fs.readFileSync(0, 'utf-8');

  function decodeBase64String(encrypted, pass) {
    var decrypted = CryptoES.AES.decrypt(encrypted, pass);
    var utf8 = CryptoES.enc.Utf8.stringify(decrypted);
    fs.writeFileSync(1, utf8);
  }

  decodeBase64String(data, pass);
} else {
  console.log('need -pass:PASS');
}

Save this file to decrypt.js.

Now, let's use NodeJS to decrypt it:

% node decrypt.js -pass:PASS < secretmsg.b64s
The file contents are displayed.

Writing the entire mobile app is a lot more involved, but figuring out the correct set of options for openssl was the most challenging, so much so, that I avoided using encryption in JavaScript for a long time. Now that I've figured it out, I can use encryption as much as I want. Hopefully, this will help you add encryption to your arsenal as well.