Tips

Encrypt data and store it in Couchbase with NativeScript

In this tutorial I'll show you how to encrypt sensitive data with CryptoJS and save it to Couchbase Mobile database on a device.

Couchbase Mobile is a NoSQL embedded database for mobile devices. It is a replacement for common database technologies like SQLite.

The sample app I created could hypothetically be used by the United States Secret Service, because it will securely store the codenames of (former) American Presidents.

To access the Couchbase Mobile database we will use the excellent NativeScript Couchbase plugin, maintained by Nic Raboy.

CryptoJS provides us with various standard and secure cryptographic algorithms. We will use the AES-256 cipher algorithm, used by the National Security Agency (NSA) for TOP SECRET information.

This tutorial uses NativeScript with Angular and iOS, my preferred way of developing {N} apps.

Because NativeScript is cross-platform you can use Android too of course. Just use the Android specific tns commands instead. Replace "ios" with "android" and you're good to go! ;-)

You can follow the steps below or clone the sample project NSNL_Couchbase from Github.

1. Create a new NativeScript project with Angular

With the following commands we create a new project and add the iOS platform.


tns create projectname --ng
cd projectname
tns platform add ios

2. Install crypto-js

crypto-js is a npm package and can be installed with the following command.


npm install crypto-js --save

3. Add the couchbase plugin

Couchbase Mobile is a NativeScript plugin and can be installed with the following command.


tns plugin add nativescript-couchbase

4. Build the project

After adding a plugin, you always have to build your project (again).


tns build ios

5. Run the project

Let's run the app to check for possible errors.


tns run ios

If you encounter the error below, please restart your computer and go back to step 4.


Error: Uncaught (in promise): ReferenceError: Can't find variable: CBLManager

6.Edit app.component.ts

Let's replace the default content with the code below.

On top we have a label to display how many documents are saved in the database.

Below that there are two buttons to load some Presidents into the app and save them to the database, and to delete them all from the app and the database.

Finally we have a list-view to display the name and encrypted codename from each President. When we tap on a President in the list, his decrypted codename will be shown.


<StackLayout>
    <StackLayout>
        <Label text="{{header}}"></Label>
        <Button text="Load & save" (tap)="loadSaveData()"></Button>
        <Button text="Delete all" (tap)="deleteData()"></Button>
    </StackLayout>
    <ListView [items]="presidentList" (itemTap)="showCodeName($event)" height="300">
        <template let-item="item">
            <StackLayout style="padding: 10">
                <Label [text]="item.name"></Label>
                <Label [text]="item.codename"></Label>
            </StackLayout>
        </template>
    </ListView>
</StackLayout>

7. Edit references.d.ts

Because we are using Angular, add the following line to references.d.ts. This will add the type definitions for this plugin. Type definitions make it possible to enjoy the benefits of type checking, autocompletion, and documentation in your IDE.


/// <reference path="./node_modules/nativescript-couchbase/couchbase.d.ts" />

8. Edit app.component.ts

We need to import the Couchbase module and CryptoJS javascript library.


import {Couchbase} from "nativescript-couchbase";
let CryptoJS = require("crypto-js");

Now remove the counter variable and the message() and onTap() methods from the AppComponent class and replace it with the code below.


private database: Couchbase;
private header: string = 'No documents found!';
private presidentList = [];
readonly VERY_SECRET_CODE: string = 'My very secret code';

We define a database, the header with default text, an array to display the Presidents in the listview and a VERY_SECRET_CODE to use as an encryption passphrase.

Note
In a real app you would never hardcode the encryption passphrase. A smarter way would be to let the user enter a passphrase or numeric code (don't forget to use .toString(), because the passphrase needs to be a string!). You use this passphrase or code every time you encrypt or decrypt the data.

Now let's add a constructor for the class, each part is explained in the comments.


public constructor() {
    // open or create a database
    this.database = new Couchbase("sampledb");
    // to store and retrieve multiple documents we need a view
    this.database.createView("sampledata", "1", function (document, emitter) {
        emitter.emit(document._id, document);
    });

    // let's retrieve the documents in this view
    let documents = this.database.executeQuery("sampledata");
    // is the first document available?
    if (documents[0]) {
        // display the number of documents in the header
        this.header = 'Found ' + (documents.length) + ' documents!';
        // loop over all documents
        for (let i = 0; i < documents.length; i++) {
            // add each document to the array
            this.presidentList.push(documents[i]);
        }
    }
}

We add two private methods to encrypt and decrypt data.


private _encryptData(data: string): string {
    // we use the AES-256 encryption -> very secure
    let cipherText = CryptoJS.AES.encrypt(data, this.VERY_SECRET_CODE);
    return cipherText;
}

private _decryptData(data: string): string {
    // first decypt to bytes
    let bytes = CryptoJS.AES.decrypt(data.toString(), this.VERY_SECRET_CODE);
    // convert bytes to text
    let plainText = bytes.toString(CryptoJS.enc.Utf8);
    return plainText;
}

And add the loadSaveData() method for the "Load & save" button.


public loadSaveData() {
    // create a president JSON object step-by-step
    // encrypt the codename
    let encryptedCodeName: string = this._encryptData('Renegade');
    // create a JSON string
    let president:string =  '{"name": "Barack Obama", "codename": "' + encryptedCodeName + '"}';
    // convert to JSON object
    let presidentObj: any = JSON.parse(president);
    // create a new document
    this.database.createDocument(presidentObj);

    // create a new document for each president in one step
    this.database.createDocument(JSON.parse('{"name": "George W. Bush", "codename": "' + this._encryptData('Trailblazer') + '"}'));
    this.database.createDocument(JSON.parse('{"name": "Bill Clinton", "codename": "' + this._encryptData('Eagle') + '"}'));
    this.database.createDocument(JSON.parse('{"name": "Ronald Reagan", "codename": "' + this._encryptData('Rawhide') + '"}'));

    // let's retrieve the documents in this view
    let documents = this.database.executeQuery("sampledata");

    // display the number of documents in the header
    this.header = 'Found ' + (documents.length) + ' documents!';

    // loop over all documents
    for (let i = 0; i < documents.length; i++) {
        // add each document to the array
        this.presidentList.push(documents[i]);
    }
}

Note
In a real app you would never load the data in the database like this, because the codenames are hardcoded in the app. You would provide a form in the app to enter the data or have a secure REST API to load the data from a trusted server.

Now add the deleteData() method for the "Delete all" button.


public deleteData() {
    // let's retrieve the documents in this view
    let documents = this.database.executeQuery("sampledata");

    // loop over all documents
    for (let i = 0; i < documents.length; i++) {
        // delete each document
        // couchbase will assign an id (_id) to a document when created
        this.database.deleteDocument(documents[i]._id);
    }
    // clear the array with presidents, this wil remove them from the listview
    this.presidentList = [];

    // change the header
    this.header = 'All documents are deleted!';
}

Finally make sure we display the decrypted code name of the tapped President.


public showCodeName(event) {
    // event contains the tapped index
    // with this index we retrieve the encrypted codename and decrypt it to be displayed
    alert(this._decryptData(this.presidentList[event.index].codename));
}

And we're done!

Please don't forget to check out the sample project NSNL_Couchbase (see animation at the top of this article) on Github, which is a more extensive example including Angular routing.