-
Notifications
You must be signed in to change notification settings - Fork 298
Database Encryption
You can optionally encrypt an entire database. The algorithm used is AES-256, which is highly secure. The database itself is encrypted using SQLCipher, a fork of SQLite, while attachments are individually encrypted in the filesystem.
STATUS: Couchbase Lite 1.1 supports encryption of SQLite databases on iOS. Version 1.2 supports ForestDB and Mac OS as well.
- If your iOS app needs to store private or confidential data, and you don't trust the users to enable passcodes on their iOS devices.
- If regulations like HIPAA require you to use encryption.
- If you develop for OS X and want to store data securely.
- iOS's default filesystem encryption is good enough for most use cases: all app files are already encrypted with a key that's unavailable when the app isn't running, provided the user has set up a device passphrase. (OS X doesn't have this by default, though a user can opt into full-disk FileVault encryption.)
- Key management can be annoying -- on the latest iOS devices you can use Touch ID, but on earlier ones you'll have to prompt the user for a passphrase on launch, since if you don't trust the built-in security that means you don't trust the Keychain to hold the key.
- Slight drop in performance -- SQLCipher claims about 5-15% overhead in database I/O.
- Larger application size, if using SQLite -- you have to embed a copy of SQLCipher instead of using the system's built-in SQLite library.
Note: These instructions are for SQLite-based databases in iOS apps. If you're using encryption only with ForestDB databases, or on Mac OS X, you don't need to change your build process at all.
- Download the appropriate SQLCipher static library from our SQLCipher release page.
- Add the SQLCipher library to your Xcode project.
- Go to the Link Binary With Libraries build phase of your app target.
- Remove
libsqlite.dylib
. - Add the SQLCipher library.
- Try building your app. It should build and run fine.
At this point, Couchbase Lite won't work any differently. Databases are still unencrypted by default.
Before creating or opening an encrypted database, you need to register its password or key:
CBLManager* mgr = [CBLManager sharedInstance];
[mgr registerEncryptionKey: @"password123456" forDatabaseNamed: @"launch-codes"];
CBLDatabase* db = [mgr databaseNamed: @"launch-codes" error: &error];
The encryption key is applied when the database is created, i.e. the first time databaseNamed:
is called. After that, the same key needs to be registered before the database can be re-opened. (It's not yet possible to add encryption to an existing database, or to change or remove the key afterwards.)
There are a couple of new error cases when opening a database:
Domain | Code | Meaning |
---|---|---|
CBLHTTP | 401 | The password is incorrect, or you didn't give a password for an encrypted database. |
CBLHTTP | 501 | Encryption is not supported (i.e. the app was linked with regular SQLite not SQLCipher.) |
We've created a utility class, CBLEncryptionController, to take care of the mundane details of password entry. It prompts the user to make up a key, asks for the key when re-opening the database, and asks again if the key was incorrect. It even takes advantage of Touch ID fingerprint authentication on recent iOS devices to avoid making the user deal with a password at all. It's available in source form from the "Extras" folder in the Couchbase Lite distribution, or from the Github repository.
Using this, opening a database looks like:
CBLEncryptionController* enc = [[CBLEncryptionController alloc] initWithDatabaseName: @"launch-codes"];
enc.parentController = self.rootViewController;
[enc openDatabaseAsync: ^(CBLDatabase *db, NSError *error) {
// now you can access the database
}];
As you can see, the API is asynchronous; you supply a callback block that will be invoked later when the database has been opened. This is necessary because opening the database is likely to require user intervention (entering a password, pressing their thumb to the sensor). More details are available in the comments in the class' header file.
The actual password-prompt UI is pretty minimal, just using UIAlertController. You will likely want to replace that with something that fits better into your app's UX, and you can do that by subclassing CBLEncryptionController. Or if you want, you can write your own controller, just using the source code of ours as a reference.
Example of use: If you want to look at a real app that uses CBLEncryptionController, go get the encryption
branch of Grocery Sync.
(This entire section is advanced material for those who aren't using CBLEncryptionController
.)
The database encryption key can be given either as an NSString
object containing a password/passphrase, or as an NSData
object containing raw key data (it must be exactly 32 bytes long, i.e. 256 bits, and any 32 bytes will work as a valid key.)
The actual encryption uses the AES algorithm, which uses 256-bit binary keys. Behind the scenes, a password string will be converted into binary data using the PBKDF2 algorithm.
So where do you get this password or key from, that you register with the CBLManager? You have to store it someplace persistent so that it can be used on the next launch of the app to decrypt the database. You have two choices, pretty much:
- Store it in the user's brain (or a sticky note or something). That is, force the user to make up a password and memorize it, and then re-enter it on every launch of the app. If the user ever forgets the password, game over -- the database is lost.
- Store it in a Touch ID-protected Keychain item, on a modern iOS device with a Touch ID (fingerprint) sensor. This is much better because the user never has to remember anything or even see the key. Too bad not everyone has an iPhone 5s or later, though.
Some places you cannot store the password:
-
Hardcoded into your app.This is easy for an attacker to extract, and that breaks the security for every single user of your app. -
In a file.The whole reason you're using encryption is because you don't trust the security of the device's filesystem. -
OK smarty, in an encrypted file.This begs the question of where you store the encryption key for that file. See above. -
In a regular Keychain item.This is slightly more secure than in a file, but in practice an attacker who can get through the filesystem encryption either has the device's passcode, or can make Keychain calls pretending to be the app.
(Why is a Touch ID-protected Keychain item safe when a regular Keychain item isn't? Because it has an additional layer of encryption provided by the secure enclave in the device's CPU, which will only decrypt the item when the user's fingerprint is present on the sensor. Hacking this requires either creating a detailed fake replica of the user's fingerprint, or some nano-scale manipulation of the running CPU chip.)
Did we mention CBLEncryptionController? It takes care of all these gnarly details for you. Why aren't you using it?
If using a user-entered password:
- It needs to be hard to guess and to provide enough entropy (randomness). The principles are pretty well known, starting with obvious quality rules like a minimum character length, broad character set (not just letters!), and so on. Unfortunately, the stronger the password is, the less tolerable it is for the user to remember and type it! Many apps give up entirely and just let the user set a short numeric code, which is completely insecure.
If generating a random binary key (for use with Touch ID, presumably):
-
Do call
SecRandomCopyBytes
as your source of data. -
Don't use a general-purpose random number generator like
random
-- it's not random enough for cryptography. - Don't try to convert a password string into a key yourself unless you know a lot about crypto, understand what PBKDF2 is and how it works, and think you can do better.
Database encryption has nothing to do with replication. The encryption only applies to data 'at rest' in your local database. The replicator still transfers documents in unencrypted form, so make sure you're using an SSL connection to the server. And the server will store documents unencrypted, so make sure the server itself is secure (which you're hopefully doing anyway.)
(End-to-end encryption of documents — such that they're replicated in encrypted form and not readable on the server — is possible, but it's more complicated. We've done it experimentally but it's not something we support yet.)
Generally you retrieve attachments as NSData
objects, but Couchbase Lite does provide one or two ways to get the path (or URL) of the file containing an attachment. If you're doing the latter, it won't work with an encrypted database, because the attachment files themselves are encrypted and not directly readable. Instead, CBLAttachment.contentURL
will be nil, and a HEAD
call to an attachment URL in the REST API will not include a Location:
header giving the path of the file.
However, we've added a new way to read large attachments efficiently. -[CBLAttachment openContentStream]
returns an NSInputStream
object through which you can read the decrypted contents of the attachment. (Just make sure to close the stream when you're done.)
It can be useful to know whether a database exists without opening it; for instance, before creating the database you'll want to ask the user to pick a password for it. There's a new method -[CBLManager databaseExistsNamed:]
that just tells you whether a database exists.