Disclaimer: I am by no means a security expert. I simply needed some client-side encryption for my project and did my best to understand the options and lay them out here. All feedback and corrections are welcome, and I apologize in advance for any mistakes!
For the past couple months I’ve been working on a side project, SoulSaga. I wanted to build a space for people to be able to reflect on their lives, their identity and their personal growth. In other words…an application with lots of sensitive user information.
Firebase solves a lot of these problems, including secure transmission and server-side encryption. But, as anyone who has used Firebase Database or Storage quickly realizes, you as an admin can see any user data that gets stored in there. So out-of-the-box, Firebase is good, but not good enough —user trust requires and deserves full protection, so some form of client-side encryption (encryption prior to sending to the database) is necessary.
In this article, I hope to enumerate some of the options I considered to address this, the final one being the one I settled on.
The encryption itself is simple enough to perform with existing libraries, using a symmetric key (a key that can both encrypt data, and decrypt the encrypted data) like AES. As the usual problem goes, we now need to find a secure place to store this all-powerful key.
If stored in your own servers, it provides no security, since you yourself could use the key to decrypt your user’s data, and therefore anyone hacking your system could do the same. It shouldn’t even be an option, so we’ll call it Option 0 :P
Option 1: Password Encrypted Keys
You could store the key on your server and ask the user to keep a strong password to encrypt the key. That’s not ideal, since it requires work on the user’s end, and if the user forgets this password, the key likely isn’t recoverable. That said, there is merit to this option, and seems to be a relatively popular one.
Option 2: Store Key on User Device
Most closely resembling end-to-end encryption, this stores the key on the user’s device, so the key is never in the application servers. The problem with this approach though, is that the key, and therefore the data, is not accessible from other devices. It’s not a bad solution, depending on the situation, but definitely not what I wanted.
Option 3: Google Key Management Service (KMS) for Key Encryption
At this point we’ve exhausted the options for where the key can be placed along this pipeline — but there are more options to consider by adding on other systems.
A popular and well-thought out solution is by Andy Geers, and involves encrypting the key with another data key stored in Google Key Management Service. The user’s key encrypts the data, and then the key is encrypted by a KMS key and stored in the database. As Andy points out,
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.
I personally wanted to design a system where the key was still in possession of the user, and did not want the burden of having to manage two accounts.
Option 4: Stash the Key in the User’s Google Account! (With User Consent)
I was already using Firebase Authentication as my authentication solution for Google accounts, so this solution was a natural extension of that. Right away, locking in to Google as a sole authentication provider is a considerable downside for this method, but I hope that this concept may inspire some, or that similar APIs exist for other authentication providers.
When the user logs in, we get the OAuth credentials to request the user’s personal encryption key, or create one if we can’t find one, from the user’s Google account. This way, the key is always fully in the user’s possession, but they never have deal with it directly. Obtaining the key would require hacking the user’s Google account, at which point the hacker could just read the data in the application anyway.
So, what does it mean to “stash” the key in someone’s Google Account? Google Drive provides an API for creating a special application data folder (user consent is required during OAuth). The contents of this folder are not visible to the user, and is only accessible via your application’s credentials!
Caveat — the user can see how much space is being occupied in their Drive by an app, and has the ability to clear the app’s data, so this method does rely on the user not accidentally deleting their own encryption key.
Thanks for reading!