Client-side AES Encryption with Meteor

…a simple step-by-step implementation

I have been working on Meteor.JS for a few years now and have recently been pondering the application of client-side data encryption for a side project that I have been working on for my girlfriend to track the grades of children in her class.

In this practical example I will demonstrate a simple method of managing data on the client by using AES encryption and a passphrase before storing the document into the server’s Mongo database.

Similarly I will also demonstrate retrieving that data back to the client and decrypting it in its original structure and format.

Outcomes

  • To create a simple application that takes a document containing a data element and stores it in the database from the client.
  • Encrypt that data element on the client using a passphrase provided by the client and store the result on the server to prevent access.
  • Retrieve the document from the server and decrypt the data element in-place on the client with minimal to no additional coding required on the client/server and provide the document to the client in its previous un-encrypted format.

Please note that this is a very basic implementation, but hopefully it will be a good basis for the concept that you can utilise in your projects.

This is a walkthrough article from start to finish, so keep scrolling to follow along the build from scratch or if you would prefer to get straight to the meat, jump down to Step 2.

Pre-requisites

This project is based on the blank “Hello World” project created using the “meteor create” command. Here are the initial commands to create the project in the same format to follow along with this article:

meteor create testproject
cd testproject
rm testproject.*
mkdir client server collections
meteor remove autopublish
meteor add coffeescript jparker:crypto-aes matb33:collection-hooks

Step 1 — Collection, Template & Pub/Sub

Firstly we need a collection to work with, inside the collections folder, add a file called collections.js and add the following code:

Test = new Mongo.Collection("test");

Okay, so the Test collection will be our focus point for this article, we now need to setup a simple template and some helpers to display the collection documents to the user.

In the client folder, create a test.html file with the following code:

<body>
{{> test}}
</body>
<template name=”test”>
<h1>Test Results</h1>
<ul>
{{#each tests}}
<li>{{data}}</li>
{{else}}
<li><em>Nothing to display</em></li>
{{/each}}
</ul>
</template>
As we will not be using any form of routing for this project, I have simply added the body tag and inserting the test template manually in the HTML file so that it will display the results on the page.

Next we need to create some javascript for this template and add some helpers to bring the data from the subscription to the list in the template.

In the client folder, create a test.js file and add the following code:

Template.test.onCreated(function() {
var instance = this;
instance.subscribe(“testData”);
});
Template.test.helpers({
tests: function() {
return Test.find();
}
});

Notice that we are subscribing to the testData publish from the server, so now we need to create that matching publish on the server to send the documents to the client.

Create a publish.js file in the server folder of the project and add the following code:

Meteor.publish(“testData”, function() {
return Test.find();
});

Now lets run the project! Type meteor at the command line and point your browser to http://localhost:3000.

First run of project with no data inserted

Now we have the basic template and data pub/sub setup, you should now see the result in the browser with the “Nothing to display” list item as the collection is currently empty.

Time to insert some data!

Open up the browsers console on the current page, here we have access to the Test collection object in the console so we can now insert some data by typing the following command:

Test.insert({data: "The quick brown fox jumps over the lazy dog"});

Insert this data (and add a few more documents if you like) and you will see the list populate with the documents that you entered in the console.

Results after adding some documents to the collection

Wohoo! now we have the basics ready, we can get to the main point of the article…

Step 2 — Encrypting the Data

In the project so far, we have inserted some documents into the collection, which has in-turn been stored on the server. All pretty straightforward stuff!

If we run the Meteor server shell, we can access all of the information in the documents the same as can be seen in the browser.

Resulst of initial imsert with no encryption
So what? you say…

Well what if this data was top-secret information that only you wanted to see? At the moment the database admin, programmer and anyone else that is allowed to subscribe to that data set can see your sensitive information! That’s not cool!

With a few handy tools from atmosphere, we can fix that!

In the pre-requisites, you added two packages, matb33:collection-hooks and jparker:crypto-aes. These two packages are the foundation for creating a client-site encryption method so that there are no keys or methods to decrypt the information on the server or even on another client without the predefined passphrase.

Firstly, we need to encrypt the data on the client before it is stored on the server, so to do that we will use the collection-hooks package and setup a function before the insert is completed.

Create a file in the client folder called helpers.js and add the following code:

Meteor.startup(function() {
Session.set(“passphrase”, “PurpleMonkeyDishwasher123”);
  Test.before.insert(function (userId, doc) {
doc.data_enc = CryptoJS.AES.encrypt(doc.data, Session.get(“passphrase”)).toString();
doc._encrpyted = true;

delete doc.data;
});
});

So what does that all mean? Here’s the outline of what we are doing here:

  • On the client we are setting a passphrase and storing it in the Session variable, this is only accessible by the client and in the current browser page. (In a real-world situation, this may be requested by the user prior to loading the subscription, or it may even be stored in LocalStorage, there are many possibilities).
  • We are hooking up the before.insert hook of the Test collection and call the AES encryption function on the data field, setting the results to the data_enc and also add the _encrypted element to the document for good measure.
  • Finally we remove the sensitive information from the document and then the insert procedure resumes and writes the data to the server.

So now that we have that function setup, clear your project by typing meteor reset or via the shell by typing Test.remove({}), and then in the browser do the same inserts as we did before.

Results of insert with data encryption on insert, no decryption

This time we get some interesting results; on the server we can now see only the encrypted information is stored in the collection and on the client, by the three bullet points, we can see that there are three documents in the collection but there is now no information showing for them them…

Why can’t we see any data on the client now?

Well that is because in the before.insert hook we removed the sensitive data before it reached the server by deleting the data element from the document and replacing it with the encrypted data_enc element so now when the server publishes the documents in the collection to the client, it only sends the information that it has which is the encrypted document.

So, the client is now subscribed to the collection and has a copy of the encrypted documents, now we need to do some more fancy coding to make the data decrypt for the client and display as normal.

Meteor’s Mongo.Collection object has a transform function that allows any document to be passed through a function for a find, findOne, #each loop etc, and this is where we will put our final piece of magic.

Now, remember that we only want to perform the decryption on the client side, so in the collections/collections.js file we created earlier, update the code with the following:

Test = new Mongo.Collection(‘test’, {
transform: function(doc) {
if (Meteor.isClient) {
if (doc._encrypted) {
doc.data = CryptoJS.AES.decrypt(doc.data_enc, Session.get(“passphrase”)).toString(CryptoJS.enc.Utf8);
delete doc.data_enc;
delete doc._encrypted;
}
}
return doc;
}
});

In summary, here’s what we’re doing:

  • For the Test collection, we are running a transform function against any document that enters the collection. If this function is being run on the client then we are decrypting the data and returning the format of the document to its original structure.
  • We get the passphrase we set earlier from the Session variable and use that to decrypt, then remove the data_enc encrypted field and the _encrypted flag as well.
  • The document on the client now looks just like the original document with a only a data element.

This time when we look at the results in the browser, without changing any of the data, instead of seeing 3 blank bullets in the list we now see the original data that we set for the documents in the collection.

Remember, these documents are still encrypted on the server, and will stay that way so without the passcode provided by the client, we cannot decode the original data from these documents!
Results of data encryption and decryption working seamlessly

Considerations

  • The encrypted data is not going to be searchable from a server-side publish. Therefore the data has to be sent to the client to be filtered or there needs to be additional meta-data added to the documents to allow for searching and indexing.
  • Updating was not covered in this article as it is beyond the scope, but would be similar to implement by hooking the “before.update” function and running the same encryption methods.
  • A package implementation of this would be very handy for simple encryption and allowing users to specify particular fields that are to be encrypted and decrypted automatically in the collection definition.

Conclusion

Hopefully this is a simple example of using AES to implement a form of client side data encryption for your sensitive data. There are some considerations above that I have thought of as some of the downsides to this while writing this article; if anyone has some suggestions or solutions, please put them in the comments!

Hope this article is useful! I have learnt 95% of Meteor from reading articles just like this so its only fair to give some back to the community!

Feel free to add me on Twitter @AaronThorp or find me on #meteor on FreeNode (oRiCLe).