The problem: protecting sensitive user data in Firebase Realtime Database
For a while now I’ve been working on implementing user accounts in the PrayerMate app so that users can sync their private data to the cloud and share it between their different devices. Think of PrayerMate as being like an Evernote equivalent but specifically focussed on recording different prayer needs – some of them ones of a highly personal nature either about yourself or about close friends that you’re praying for.
Ever since Google announced their revamped version of Firebase a year ago I’ve been in love with it as a platform – the Firebase Realtime Database in particular makes authenticating users and syncing their data to the cloud as easy as pie. But there’s one big drawback: anybody with admin credentials for the Firebase project can browse all of that private user data at will. Equally, Google’s infrastructure is pretty rock solid in terms of security (I went to a presentation about it at the recent Google Cloud Next conference in London – and it is seriously cool stuff) but the consequences of a hacker getting hold of all of that user data doesn’t even bear thinking about.
That’s why I’ve been looking for a solution that allows users to still sync all of their data between their devices via Firebase, but whilst preventing me as the developer (or anybody else who somehow got hold of a data dump) from reading that data. At the same time, I wanted to do it in a way that meant a user who lost their phone wasn’t completely locked out of all of their data for all time – even people with just a single device are frequently asking me to implement sync as a backup mechanism.
Thankfully Google’s own infrastructure provides some really cool tools that made solving this surprisingly easy – and since even people within Google / Firebase themselves didn’t seem overly aware of what was on offer, I thought it was worth blogging about my experiences.
Step 1: A cross-platform encryption/decryption solution – RNCryptor
My first day of this project was spent looking for an encryption/decryption library that met the following requirements:
- Data could be decrypted across both iOS and Android phones
- Production ready / stable
- Actively maintained
- Actually secure
- Performant (fast encryption / decryption)
Considering the year is 2017 this was a remarkably difficult exercise. Almost every library I came across had huge warnings either on the iOS version or the Android version saying “The library on the other platform uses really insecure defaults which I had to incorporate for compatibility purposes”, or it hadn’t been touched in four years, or it had a gazillion issues logged against it.
I eventually settled on RNCryptor-objc / RNCryptorNative. When I last looked at RNCryptor a few years back the only option on Android was JNCryptor which was ridiculously slow (multiple seconds per operation) and which I now notice is covered in warnings saying “Do not use on Android”.
Step 2: Securely synced encryption keys using Google Cloud KMS
With RNCryptor implemented on both platforms, that just left the little issue of how to actually sync the user’s encryption key between their devices. A naive solution would be to store that key within the Firebase database itself – that would at least prevent me from accidentally reading people’s private data (at least nothing would be stored in plain text) but would still make it trivial for anybody with access to the Firebase data to decrypt anything they wanted to.
At Google Cloud Next I came across the Google Cloud Key Management Service, and could immediately tell there was some potential here. The Google Cloud KMS lets you create encryption keys which can then be used to encrypt and decrypt data. My first assumption was that I’d generate a key for every user, but for PrayerMate’s 25,000 monthly active users that would quickly reach at least $1,500 every single month. After a chat with some of the very helpful Google Developer Advocates I quickly realised that wasn’t what I needed at all – just a single KMS key could be used to encrypt and decrypt a user’s data encryption key (DEK).
In the end what I came up with was to build a super-simple authentication service in the Google App Engine Flexible Environment. When a user first logs in to PrayerMate, the auth service generates them a new DEK which it gives to them, as well as encrypting it in KMS for storage in Firebase. When the user logs in to their second device, the auth service takes the encrypted key from Firebase and again uses KMS to decrypt it for the user to store in their device’s local keychain (where it is again stored in encrypted form).
Importantly, the KMS key belongs to a different Google account to the Firebase database, so no one user (e.g. me) has permission to both read the data AND decrypt it. A hacker would need to compromise both accounts to access the unencrypted data.
Step 3: Authentication of Firebase users via Google Cloud Endpoints
Where things get REALLY cool, however, is with the introduction of Google Cloud Endpoints in to the mix. This is basically a proxy layer that sits between your user and your backend, but which, crucially, understands the concept of Firebase authenticated users and can validate those logins and tell your backend who somebody is.
This means that each time we generate a new DEK for a user we can encrypt it along with their user ID, so that when somebody comes along later requesting to decrypt a particular DEK the backend can verify if it actually belongs to them.
On the whole, the documentation for Google Cloud Endpoints is pretty good, and if you persevere long enough you can probably figure out how to get it working. I got stuck on a couple of points: firstly, I got myself in a muddle about what to put in the
endpoints_api_service section of
app.yaml and how it related to the
host property of
openapi.yaml. There are so many different deployment combinations that the Endpoints documentation struggles to make it very clear – but if you are deploying to the flexible app engine you just use the same
[PROJECT_ID].appspot.com form in both places, and your
endpoints_api_service.config_id is just what you get given when you deploy your proxy configuration using
gcloud service-management deploy openapi.yaml (usually something like “2017-01-01r0”).
The second place where I got really stuck for a while is how to actually enable Firebase authentication on the backend. The Endpoints documentation talked about a
X-Endpoint-API-UserInfo header but for the life of me I could not get it to be injected at all. Eventually, I discovered the missing instruction from the documentation (and hopefully they’ll soon accept my request to fix that): after you have added your
firebase entry to the
securityDefinitions section of
openapi.yaml you then ALSO need to actually use that security definition by adding a section like this:
security: - firebase: 
Update: I have now open-sourced the code for my backend service as firebase-keysafe. Contributions would be welcome if you spot room for improvement.
Fancy working for PrayerMate?
Android Developer Wanted
If solving interesting problems like this sounds like your cup of tea, all in the aid of helping the world to pray more, then you should know that I’m currently on the look out for a full time Android developer – ideally based in London – either on a short-term contract or more permanent. I’m looking for somebody who is fully committed to the aims of a Christian prayer app like PrayerMate and who is able to be more than just a code-monkey following a tightly defined spec but instead is able to partner in helping build the best possible prayer platform to mobilise the Christian church to pray. If that sounds like you then please get in touch!