First: this library only makes sense if your clients sit behind a firewall, or if you control who can get an account and anonymous users don't need to access encrypted data. Otherwise, the encryption key will be public defeating the whole point.
This library monkey-patches either the Firebase Admin SDK or the Firebase JavaScript Client SDK to automatically encrypt and decrypt keys and values of your choosing using AES-SIV. Almost everything just works, except that startAt
and endAt
queries on encrypted data would produce randomly ordered results and so are forbidden. equalTo
queries will work fine, however, since a given plaintext value will always encrypt to the same ciphertext — but it will also let an attacker know if any two values are equal, even if they don't know what they are.
The library works both in Node (4.x+) and in the browser. In the browser, you need to also load crypto-js
(the following modules are sufficient: core.js
, enc-base64.js
, md5.js
, evpkdf.js
, cipher-core.js
, aes.js
, mode-ctr.js
) and cryptojs-extension
(only build/siv.js
is required). If you want to enable caching to enhance performance, then in the browser you'll also want to load node-lru-cache
. All these libraries are automatically included in the Node distribution.
Upon requiring this library, the admin.database()
or firebase.database()
method as well as the
app.App.database()
methods are monkey-patched to return a custom FireCrypt
instance in place of
standard admin.database.Database
or firebase.database.Database
instances. The FireCrypt
instance generally has the same API, although you must first configure the encryption before it
can be used:
Configure FireCrypt With Firebase Admin SDK:
const admin = require('firebase-admin');
const FireCrypt = require('firecrypt');
const app = admin.initializeApp({
// ...
})
const db = admin.database(); // OR const db = app.database();
const encryptionKeyCheckValue = db.configureEncryption(options, specification);
Configure FireCrypt With Firebase Client SDK:
const firebase = require('firebase');
const FireCrypt = require('firecrypt');
const app = firebase.initializeApp({
// ...
})
const db = firebase.database(); // OR const db = app.database();
const encryptionKeyCheckValue = db.configureEncryption(options, specification);
The options
are as follows:
algorithm
: the crypto algorithm to use. Currently supported values are:aes-siv
: actual encryption using AES-SIV.passthrough
: fake encryption using an identity transform, useful for debugging.none
: no encryption, will throw an error withfirecrypt === 'NO_KEY'
if you attempt to read or write any encrypted path.
key
: the required key for algorithmaes-siv
. Must be 32, 48, or 64 bytes, encoded in base 64. You can generate such a key usingopenssl rand -base64 64
. If you attempt to decrypt a value with the wrong key then an error withfirecrypt === 'WRONG_KEY'
will be thrown.encryptionKeyCheckValue
: a value generated by a previous call toconfigureEncryption()
used to verify that theaes-siv
key
used in both calls is the same. If a different key was used to generate theencryptionKeyCheckValue
then an error withfirecrypt === 'WRONG_KEY'
will be thrown.- For convenience, the
encryptionKeyCheckValue
is also available at any time viaadmin.database().encryptionKeyCheckValue
orfirebase.database().encryptionKeyCheckValue
.
- For convenience, the
cacheSize
: the maximum size in bytes of the encryption and decryption caches, used to improve performance. In the browser, the caches will only be activated ifLRUCache
is defined; it should conform to the API ofnode-lru-cache
. You can also specifyencryptionCacheSize
anddecryptionCacheSize
separately.
If the algorithm
is aes-siv
then configureEncryption()
will return a value that can be used to synchronously verify whether another key matches by passing it via encryptionKeyCheckValue
. This can be useful if the key is distributed as part of a session, and you want to check if you need to invalidate the session because the key has been rotated. Also, if the key doesn't match then decrypting will throw an exception later.
The specification
is a JSON structure similar to Firebase security rules but specifying which keys and values need to be encrypted instead. The structure mimics that of your datastore and uses $wildcards
in the same manner as security rules.
{
"rules": {
"foo": {
".encrypt": {"value": "#"}
},
"bar": {
"$baz": {
".encrypt": {"key": "#-#-."}
}
}
}
}
Each .encrypt
directive can require the key or value (or both) at that path to be encrypted. The parameter is an encryption pattern, where #
are placeholders for chunks to be encrypted, .
for chunks that should not be encrypted, and everything else is matched verbatim to the plaintext data. Normally, you'll just use a single #
to encrypt the entire key or value, but sometimes it can be useful to encrypt only specific parts of a composite key. You can also specify an empty pattern to explicitly indicate that something should not be encrypted, which is only useful if you're encrypting a sibling wildcard key but don't want some specific instances to be encrypted.
You must specify value encryption at the atomic data leaves only — it's not valid to encrypt an object and trying to do so will throw an exception at runtime. There's currently no way to require encryption for an entire subtree.
For bulk encryption/decryption (including key rotation), you can also specify ".encrypt": {"few": true}
on wildcard keys (whether encrypted or not) where the number of children is expected to be low enough that it's reasonable to read or write them all at once.
You may want to check out fireplan
for a convenient way to generate the encryption specification from your security rules schema. See also firecrypt-tools
for related utilities.
Run the following commands from the command line to get your local environment set up:
$ git clone [email protected]:Reviewable/firecrypt.git
$ cd firecrypt # go to the firecrypt directory
$ npm install # install local npm dependencies
Run the following command to build the distribution files for the library:
$ npm run build
This will generate the following distribution files, along with accompanying source maps:
dist/node/firecrypt.js
- A non-minified CommonJS build of the library for use in Node.js.dist/browser/firecrypt.js
- A non-minified IIFE build of the library for use in the browser.dist/browser/firecrypt.min.js
- A minified IIFE build of the library for use in the browser.