Skip to content

Instantly share code, notes, and snippets.

@lbrenman
Last active June 3, 2016 17:11
Show Gist options
  • Save lbrenman/24b9765a460ee6855fd1 to your computer and use it in GitHub Desktop.
Save lbrenman/24b9765a460ee6855fd1 to your computer and use it in GitHub Desktop.
Appcelerator Arrow - Encrypt Data in Transit

Arrow Encryption for Data in Transit

Appcelerator mobile applications use Secure Sockets Layer (SSL) for encrypting and decrypting all data transmitted and received by the device. However, for certain applications, one may want to add another layer of encryption for added security. This post describes how to programmatically add additional encryption for data in transit between an Appcelerator application and an ArrowDB as illustrated below.

Background

The basic idea is to add a pre block to your Arrow model for decrypting data on a POST or PUT from the client app. This will decrypt data sent by the client app. Also, add a post block for encrypting data being sent to the client app on a GET.

For the purpose of this post, we will use a very simple XOR encryption JavaScript function from Henry Algus which is also the decryption function and is shown below:

exports.jsEncode = function(s,k) {
	var enc = "";
	var str = "";
	str = s.toString();
	for (var i = 0; i < s.length; i++) {
		var a = s.charCodeAt(i);
		var b = a ^ k;
		enc = enc + String.fromCharCode(b);
	}
	return enc;
}

Furthermore, since the Appcelerator Platform is a full stack JavaScript Platform, this exact same function is used for both encryption and decryption in both the Appcelerator mobile app and the Arrow app.

Note that this XOR encryption should not be used for production applications and is strictly being used for demonstration purposes. In a production application, you may want to look at more secure encryption libraries such as the Appcelerator Premium "Crypto" Module, Securely, crypto-js or sjcl.

Arrow Model

Here is a simple ArrowDB model, database, and a pre (decrypt) and post ((encrypt)) block that illustrates how to encrypt and decrypt data in Arrow:

database.js

var Arrow = require("arrow");
var Model = Arrow.createModel("database",{
	"fields": {
		"name": {
			"type": "String"
		}
	},
	"connector": "appc.arrowdb",
	"actions": [
		"create",
		"read",
		"update",
		"delete",
		"deleteAll"
	],
	"before": "decrypt",
	"after": "encrypt",
	"singular": "database",
	"plural": "databases"
});
module.exports = Model;

decrypt.js

var ENC_KEY = '123';
var Arrow = require('arrow');
var Utils = require('../lib/utils');
var PreBlock = Arrow.Block.extend({
	name: 'decrypt',
	description: 'will decrypt data from mobile app',
	action: function (req, resp, next) {
		if((req.method==="POST" || req.method==="PUT")) {
			req.params.name = Utils.jsEncode(req.params.name, ENC_KEY);
		}
		next();
	}
});
module.exports = PreBlock;

encrypt.js

var ENC_KEY = '123';
var Arrow = require('arrow');
var Utils = require('../lib/utils');
var PostBlock = Arrow.Block.extend({
	name: 'encrypt',
	description: 'will encrypt data to mobile app',
	action: function (req, resp, next) {
		if(req.method==="GET") {
			var body = JSON.parse(resp.body);
			var data = body[body.key];
			var dataLen = data.length;
			if(dataLen){ //findAll
				data.forEach(function (_row, _index) {
					_row.name = Utils.jsEncode(data[_index].name, ENC_KEY);
				});
			} else { //findOne
				if(data.name) {
						data.name = Utils.jsEncode(data.name, ENC_KEY);
				}
			}
			resp.success(body[body.key], next);
		} else {
				next();
		}
	}
});
module.exports = PostBlock;

In the example above, a key of '123' is used to encrypt and decrypt the data.

Mobile App

The mobile application should encrypt the data prior to POSTing data using the jsEncode function with the same key that is used in the Arrow app (e.g. '123'). The example below illustrates encrypting the value of a TextField with an Alloy id of "nameTF" prior to POSTing the data in an Appcelerator mobile app.

var ENC_KEY = '123';
var xhr = Ti.Network.createHTTPClient({
    onload: function onLoad() {
        Ti.API.info("Loaded: " + this.status + ": " + this.responseText);
    },
    onerror: function onError() {
        Ti.API.info("Errored: " + this.status + ": " + this.responseText);
    }
});
xhr.open("POST","http://127.0.0.1:8080/api/database");
var authstr = 'Basic ' + Ti.Utils.base64encode('TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG:');
xhr.setRequestHeader("Authorization", authstr);
xhr.setRequestHeader("Content-Type","application/json");
xhr.send(JSON.stringify({
    "name": jsEncode($.nameTF.value, ENC_KEY)
}));

After a GET is called, the encrypted data received by the Appcelerator mobile app must be decrypted as illustrated below. In the example below, the received data is used to populate a TableView with an Alloy id of "TV".

var ENC_KEY = '123';
var xhr = Ti.Network.createHTTPClient({
    onload: function onLoad() {
        Ti.API.info("Loaded: " + this.status + ": " + this.responseText);
        var body = JSON.parse(this.responseText);
        var data = body[body.key];
        var rows = [];
        if(data.length>0) {
          _.each(data, function(item) {
              rows.push(Alloy.createController('row', {
              name: jsEncode(item.name, ENC_KEY)
            }).getView());
          });
        }
        $.TV.setData(rows);
    },
    onerror: function onError() {
        Ti.API.info("Errored: " + this.status + ": " + this.responseText);
    }
});
xhr.open("GET","http://127.0.0.1:8080/api/database");
var authstr = 'Basic ' + Ti.Utils.base64encode('TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG:');
xhr.setRequestHeader("Authorization", authstr);
xhr.send();

Viewing Data in ArrowDB

Recall that we are inly encrypting the data in transit. The data in the ArrowDB is unencrypted. However, since we have added an encryption post block to the model for all GET operations, when we try to view the ArrowDB data in the Arrow console, we will see encrypted data as shown below:

So, how do we view/access the unencrypted data?

One way is to use the Appcelerator Dashboard, select the Arrow app and navigate to the ArrowDB, and view the data in the Manage Data -> Custom Object section as shown below:

Another technique for programmatically receiving unencrypted data is to modify the post block to look for a specific header and value and then send the data unencrypted. This code is provided here.

An example curl command using this technique is shown below:

curl -u TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG: "http://127.0.0.1:8080/api/database" -H "admin-secret:admin-secret"

Results in the following reply:

{
    "success": true,
    "request-id": "b2a5d659-516d-49ba-a33e-629de4bab195",
    "key": "databases",
    "databases": [
        {
            "id": "564357e0dc26a0090d2f5b82",
            "name": "Leor"
        },
        {
            "id": "563d79043d24bb09100b7f6c",
            "name": "Lillian"
        },
        {
            "id": "563d78c8dc26a0090d0adfb3",
            "name": "Kim"
        },
        {
            "id": "563d78afdc26a009050af1b3",
            "name": "Nate"
        }
    ]
}

Compare this to using the curl command without the header:

curl -u TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG: "http://127.0.0.1:8080/api/database"

which returns the following encrypted data:

{
    "success": true,
    "request-id": "bf8cbf08-d2f2-4a24-a9a5-a37c0841535c",
    "key": "databases",
    "databases": [
        {
            "id": "564357e0dc26a0090d2f5b82",
            "name": "7\u001e\u0014\t"
        },
        {
            "id": "563d79043d24bb09100b7f6c",
            "name": "7\u0012\u0017\u0017\u0012\u001a\u0015"
        },
        {
            "id": "563d78c8dc26a0090d0adfb3",
            "name": "0\u0012\u0016"
        },
        {
            "id": "563d78afdc26a009050af1b3",
            "name": "5\u001a\u000f\u001e"
        }
    ]
}

Summary

This post demonstrates how easy Arrow makes it to encrypt data in transit. Code for this post can be found here

var Arrow = require("arrow");
var Model = Arrow.createModel("database",{
"fields": {
"name": {
"type": "String"
}
},
"connector": "appc.arrowdb",
"actions": [
"create",
"read",
"update",
"delete",
"deleteAll"
],
"before": "decrypt",
"after": "encrypt",
"singular": "database",
"plural": "databases"
});
module.exports = Model;
var Arrow = require('arrow');
var Utils = require('../lib/utils');
var PreBlock = Arrow.Block.extend({
name: 'decrypt',
description: 'will decrypt data from mobile app',
action: function (req, resp, next) {
// console.log("decrypt block run");
if((req.method==="POST" || req.method==="PUT")) {
console.log("req.params.name = "+JSON.stringify(req.params.name));
req.params.name = Utils.jsEncode(req.params.name, '123');
}
next();
}
});
module.exports = PreBlock;
var Arrow = require('arrow');
var Utils = require('../lib/utils');
var PostBlock = Arrow.Block.extend({
name: 'encrypt',
description: 'will encrypt data to mobile app',
action: function (req, resp, next) {
// console.log("encrypt block run");
var encrypt = !(req.headers['admin-secret'] && req.headers['admin-secret'] === 'admin-secret');
if(encrypt) {
if(req.method==="GET") {
console.log("resp.body = "+JSON.stringify(resp.body));
var body = JSON.parse(resp.body);
var data = body[body.key];
var dataLen = data.length;
if(dataLen){ //findAll
data.forEach(function (_row, _index) {
_row.name = Utils.jsEncode(data[_index].name, '123');
});
} else { //findOne
if(data.name) {
data.name = Utils.jsEncode(data.name, '123');
}
}
resp.success(body[body.key], next);
} else {
next();
}
} else {
next();
}
}
});
module.exports = PostBlock;
function jsEncode(s,k) {
//http://www.henryalgus.com/creating-basic-javascript-encryption-between-frontend-and-backend/
var enc = "";
var str = "";
str = s.toString();
for (var i = 0; i < s.length; i++) {
var a = s.charCodeAt(i);
var b = a ^ k;
enc = enc + String.fromCharCode(b);
}
return enc;
}
function getData() {
var xhr = Ti.Network.createHTTPClient({
onload: function onLoad() {
Ti.API.info("Loaded: " + this.status + ": " + this.responseText);
var body = JSON.parse(this.responseText);
var data = body[body.key];
populateTable(data);
},
onerror: function onError() {
Ti.API.info("Errored: " + this.status + ": " + this.responseText);
}
});
xhr.open("GET","http://127.0.0.1:8080/api/database");
var authstr = 'Basic ' + Ti.Utils.base64encode('TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG:');
xhr.setRequestHeader("Authorization", authstr);
xhr.send();
}
function setData() {
var xhr = Ti.Network.createHTTPClient({
onload: function onLoad() {
Ti.API.info("Loaded: " + this.status + ": " + this.responseText);
getData();
},
onerror: function onError() {
Ti.API.info("Errored: " + this.status + ": " + this.responseText);
}
});
xhr.open("POST","http://127.0.0.1:8080/api/database");
var authstr = 'Basic ' + Ti.Utils.base64encode('TCDiEh3jpghZ2rQ7ckSr1NhGo2AZURwG:');
xhr.setRequestHeader("Authorization", authstr);
xhr.setRequestHeader("Content-Type","application/json");
var data = $.nameTF.value;
var dataEncrypt = jsEncode(data, '123');
Ti.API.info("Data (Original) = "+data);
Ti.API.info("Data (Encrypted) = "+dataEncrypt);
xhr.send(JSON.stringify({
"name": dataEncrypt
}));
}
function populateTable(data) {
Ti.API.info("populateTable: data = "+JSON.stringify(data));
var rows = [];
if(data.length>0) {
_.each(data, function(item) {
var dataEncrypt = item.name;
var dataDecrypt = jsEncode(item.name, '123');
rows.push(Alloy.createController('row', {
name: dataDecrypt
}).getView());
});
}
$.TV.setData(rows);
}
$.index.open();
getData();
<Alloy>
<Window>
<Label top="30">Encrypt Demo</Label>
<TextField id="nameTF" hintText="Enter name" />
<Button title="Submit" onClick="setData"/>
<Label top="10">Server Data</Label>
<Button title="Get" onClick="getData" />
<TableView id="TV"/>
</Window>
</Alloy>
var args = arguments[0] || {};
$.nameLBL.text = args.name;
<Alloy>
<TableViewRow id="TVR">
<Label left="10" id="nameLBL"/>
</TableViewRow>
</Alloy>
exports.jsEncode = function(s,k) {
//http://www.henryalgus.com/creating-basic-javascript-encryption-between-frontend-and-backend/
var enc = "";
var str = "";
str = s.toString();
for (var i = 0; i < s.length; i++) {
var a = s.charCodeAt(i);
var b = a ^ k;
enc = enc + String.fromCharCode(b);
}
return enc;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment