Client side encryption for Couchbase Mobile

In the previous post we showed how you can integrate IQrypt SDK with Azure Mobile Apps by building a Windows Universal App which encrypts the data at the client side and store it further in SQL Azure.

Today  we’ll show how you can integrate IQrypt SDK with Couchbase Mobile. Couchbase Mobile empowers you to build mobile apps that store data locally and keep it in sync with Couchbase Server in the cloud via Sync Gateway. Couchbase Mobile already offers local database encryption and that is good, but this is not enough, as we showed in one of our blog posts.

We will make a demo app called DoctorsAgenda, it is a Xamarin Forms app that can run on Android and iOS and it uses latest version of Couchbase Lite SDK for .NET. The application is supposed to be used by a Doctor who makes visits to his patients in a hospital. The doctor can see his schedule and also can see patients medical data (which is stored locally and synchronized with Couchbase Server in the cloud) and then mark the patient as ‘Visited’. We will encrypt patient data at the client side( on the device) before the data is stored locally or sent to the cloud. At the end, the app will look like this on Android:

doctorsAgendaAndroid doctorsAgendaAndroid1

And like this on iOS:

doctorsAgendaiOS doctorsAgendaiOs1

Backend setup

First, let’s prepare the backend, we will install Couchbase Server and Couchbase Sync Gateway. For this demo we’ll use a Windows Server VM on Microsoft Azure. The installation on Windows is straight forward: download Couchbase Server Comunity Edition version 4.0.0 and then just Next, Next, etc. After the installation, it will be opened a browser with http://localhost:8091/ where you have to continue the setup (I let everything default). After the setup is done, go to ‘Data Buckets’ and, on the right-up corner, press ‘Create New Data Bucket’ and put as bucket name:patients

couchbaseIQrypt11

Now the server is ready, we’ll need to install Couchbase Sync Gateway; download it from the same location and install everything with default settings.

After the Sync Gateway is installed we’ll need to configure and link it with Couchbase Server.

So go to C:\Program Files (x86)\Couchbase\ and edit serviceconfig.json and modify it like this:

 
{
   "interface":":4984",
   "adminInterface":":4985",
   "log":["REST"],
   "databases":{
      "patients":{
         "users": {"GUEST": {"disabled": false, "all_channels": ["*"], "admin_channels": ["*"]}},
         "server":"http://localhost:8091",
         "bucket":"patients",
         "sync":`function(doc) {channel(doc.channels);}`
      }
   }
}

after that, start the Sync Gateway with this config file as argument:

couchbaseSyncGateway_started

Now the Sync Gateway can be accessed from localhost, but you will still have to do some extra settings to make it available on internet:

  • Open port 4984 in Windows Firewall on the Azure VM itself
  • Add a End Point on Azure VM and put ‘CB_sync_gateway’ for port 4984 (as public port and as private port)

Now our Sync Gateway can be accessed from outside( just be aware that we did no added any authentication and authorization mechanism, for production setups, follow Couchbase documentation about it).

syncGatewayFromOutside

Client setup

Let’s start with our data model, first Patient entity:

 
public class Patient
    {
        
        public string ObjectId { get; set; }
        public string PhotoThumbnail { get; set; }
        public string PhotoDetail { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Gender { get; set; }
        public string Location { get; set; }
        public string Building { get; set; }
        public string SSN { get; set; }
        public int Floor { get; set; }
        private DateTime _agenda_time;
        public DateTime AgendaTime 
        {
            get
            {
                return _agenda_time;
            }
            set
            {
                _agenda_time = value;
                String dtString = _agenda_time.Hour.ToString("00") + ":" + _agenda_time.Minute.ToString("00");
                AgendaHourMinute = dtString;
            }
        }

        public string AgendaHourMinute { get; set; }
        public string Treatment { get; set; }
        public string Status { get; set; }
        public string Doctor { get; set; }
        public string Version { get; set; }
        public PatientDetails PatientDetailsData { get; set; }
.......
}

PatientDetails contains other additional data (the definition can be found on Github repository).
The goal is that an entire Patient object is encrypted with RND encryption scheme and we will extract 2 tags that we want to search by:

  • SSN (Social Security Number) will be encrypted with DET so we can make equality queries
  • AgendaTime will be encrypted with OPE so we can make range queries

Couchbase stores data as JSON documents. Every document has some predefined properties/fields like ‘Id’ and ‘Rev’ and you can add a dynamic number of other properties/fields. In our use case we will have 3 properties: Patient, SSN and AgendaTime, so our JSON doc(encrypted) will look like this:

 
{
"Id":"-iHqlStW3Ike1B23datn4WQ",
"Patient":"flLjX9PhwaHlMn50uyJVyYllJUpsrK7ejfMadfadfsdfk99e3fjcj",
"SSN":"SYs8rlpHsUEV555Yp2ltGS7Qkon7yJy54pXgnPuQmSs=",
"AgendaTime":"@@@2103079269433"
.....
}

So, basically, within Patient property will be stored the entire Patient object encrypted with RND, within SSN will be store encrypted SSN value of the patient and similar for AgendaTime.

Now we will create a repository interface; its implementors will manage Couchbase Lite documents.

 

public interface IPatientRepository
 {
 
     Task<bool> ExistsPatients();
     Task<Patient> Get(string key);
     Task<IList<Patient>> GetByAgenda(DateTime agendatime, int limit);
     Task<Patient> GetBySSN(string sSN);
     Task Store(Patient item);
     Task StoreBatch(IList<Patient> items);
     Task Delete(Patient item);
     Task SynchronizeWithCloud();
 }

Now let’s start implementing the methods of this interface. We would need to add reference to Nuget packages:

Then compile IQrypt-iOS and IQrypt-Android projects from src/IQrypt/ and use IQrypt.dll from its /bin folders as references within DoctorsAgenda.iOS and DoctorsAgenda.Droid projects.

Then we will implement our repo; first we’ll do some initialization:

using Couchbase.Lite;
using IQrypt;
.....

 class CouchbaseMobileRepository : IPatientRepository
    {
        Manager dbmanager;
        Database database;
        IEncryptor encryptorRND;
        IEncryptor encryptorDET;
        IEncryptor encryptorOPE;

        public CouchbaseMobileRepository()
        {
            //initialize couchbase stuff
            dbmanager = Manager.SharedInstance;
            database = dbmanager.GetDatabase("patients");

            var ssnView = database.GetView("patientsBySSN");
            ssnView.SetMap((doc, emit) => emit((string)doc["SSN"], null), "2");

            var agendaView = database.GetView("patientsByAgendaDate");
            agendaView.SetMap((doc, emit) => emit((string)doc["AgendaTime"], null), "2");


            //initialize IQrypt
            IQryptConfigurator.SetEncryptionChiper(Cipher.AES256, "mysuper_secret");
            encryptorRND = EncryptorFactory.GetEncryptor(EncryptionType.RND);
            encryptorDET = EncryptorFactory.GetEncryptor(EncryptionType.DET);
            encryptorOPE = EncryptorFactory.GetEncryptor(EncryptionType.OPE);
        }
........

We have created above a Couchbase Lite database called ‘patients’ and two views: pattientsBySSN and patientsByAgendaDate.  Couchbase Views allows applications to create and maintain secondary indexes, so we can further query by SSN and AgendaTime( we’ll see further how).

We have also set IQrypt cipher + password(encryption key material) and create encryptors instances for the encryption schemes we need.

The app will check if we have any patients stored and if not, it will generate some patients with random info and then it will call StoreBatch method. So let’s implement this method:

 

public async Task StoreBatch(IList<Patient> patients)
        {
            foreach (var patient in patients)
            {
                var document = database.CreateDocument();

                var properties = new Dictionary<string, object>();
                properties["patient"] = encryptorRND.Encrypt(patient);
                properties["SSN"] = encryptorDET.Encrypt(patient.SSN);
                properties["AgendaTime"] = encryptorOPE.Encrypt(patient.AgendaTime);

                var revision = document.PutProperties(properties);

                patient.ObjectId = document.Id;
            }
        }

So, the data is encrypted with respective encryption schemes before the document is stored, so if it will further Sync with Couchbase Server, it will remain also encrypted.

We will implement now the methods that execute queries, first GetBySSN:

 public async Task<Patient> GetBySSN(string sSN)
        {
            Query query = database.GetView("patientsBySSN").CreateQuery();
            string SSNEncrypted = encryptorDET.Encrypt(sSN);

            query.Keys = new List() { SSNEncrypted };
            query.Limit = 1;
            var result = await query.RunAsync();
            var row = result.ToList().FirstOrDefault();
            if (row != null)
            {
                var documentProperties = row.Document.Properties;

                string patientEncrypted = documentProperties["patient"].ToString();
                Patient p = encryptorRND.Decrypt(patientEncrypted, typeof(Patient)) as Patient;

                p.ObjectId = row.DocumentId;
                return p;
            }
            return null;
        }

We encrypt first SSN and send its encrypted value to Couchbase engine which executes the query without decrypting the SSN.

Let’s see now how we can execute a range query using OPE scheme:

public async Task<IList<Patient>> GetByAgenda(DateTime agendatime, int limit)
        {
            Query query = database.GetView("patientsByAgendaDate").CreateQuery();
            string start = encryptorOPE.Encrypt(agendatime.Date);
            string end = encryptorOPE.Encrypt(agendatime.AddDays(1).Date);

            query.StartKey = start;
            query.EndKey = end;
            query.Limit = limit;

            var result = await query.RunAsync();
            var rows = result.ToList();
            List list = new List();
            foreach (var row in rows)
            {
                var documentProperties = row.Document.Properties;

                string patientEncrypted = documentProperties["patient"].ToString();
                Patient p = encryptorRND.Decrypt(patientEncrypted, typeof(Patient)) as Patient;

                p.ObjectId = row.DocumentId;
                list.Add(p);

            }
            return list;
        }

So we have encrypted first the ‘start’ and ‘end’ values and then set as StartKey and EndKey, respectively. The Couchbase engine will load only the documents within the specified range (without decrypting anything).

The data is stored locally so far. We will need to Sync with our backend, so let’s implement the method for Sync:

 public async Task SynchronizeWithCloud()
        {
            Replication pull = database.CreatePullReplication(CreateSyncUri());
            Replication push = database.CreatePushReplication(CreateSyncUri());
           
            pull.Start();
            push.Start();
            
        }
        Uri CreateSyncUri()
        {
            Uri syncUri = null;
            string scheme = "http";
            string host = "yourserver.cloudapp.net";
            int port = 4984;
            string dbName = "patients";

            var uriBuilder = new UriBuilder(scheme, host, port, dbName);
            syncUri = uriBuilder.Uri;

            return syncUri;
        }

After we’ll call this method, Replication process will start and will pull and push changes from/to Couchbase Server.
After the initial Sync(we generate some patients if db is empty), we can go on Couchbase Server and see how the data is stored:

couchbaseIQrypt1

And editing a Document:

couchbaseIQrypt2

So, as visible, everything remain encrypted, though the documents remains “browseable”.

Full source code can be found on Github.

Encrypt and stay safe!