Android’s document store now supports encryption of all data inside your database using 256-bit AES: JSON documents, Query indexes and attachments.
JSON documents and Query indexes are stored in SQLite databases. We use SQLCipher to encrypt the data in those databases. Attachment data is stored as binary blobs on the filesystem. We use the Android JCE implementation for this, using 256-bit AES in CBC mode. Each distinct file blob gets its own IV, stored with the file on disk.
If you plan to use encryption, first you need to download the SQLCipher
.jar and .so binary files to include them in your application.
We support version 3.2 and higher (3.2 and 3.3 tested).
Download the libraries and follow the tutorial at the SQLCipher website. We've included a summary below.
Be sure to download version 3.2 and up; at the time or writing the page linked above has 3.3 but the tutorial documentation is still linking to 3.1.
First add the downloaded binaries to the appropriate folders within your app structure:
- Add shared library files and SQLCipher jar to
jniLibsdirectory under the Android app directory. Example from sync-android AndroidTest app. - Add required ICU zip to your app's
assetfolder. Example from sync-android AndroidTest app: - Add
sqlcipher.jarto the Java Build Path. This can be added in the Dependencies tab, which is found under the Open Module Settings in the Android Studio app folder context menu.
Once you've included the binaries in your app's build, you need to perform some set up in code:
-
Load SQLCipher library. This line needs to be done before any database calls:
SQLiteDatabase.loadLibs(this);
-
With encryption, two parameters are required in the
getInstancecall: the application storage path and aKeyProviderobject. TheKeyProviderinterface can be instantiated with theSimpleKeyProviderclass, which just provides a developer or user set encryption key. Create a new SimpleKeyProvider by providing a 256-bit key as abytearray in the constructor. For example:// Security risk here: hard-coded key. // We recommend using java.util.SecureRandom to generate your key, then // storing securely or retrieving it from elsewhere. // Or use AndroidKeyProvider, which does this for you. byte[] key = "testAKeyPasswordtestAKeyPassword".getBytes(); KeyProvider keyProvider = new SimpleKeyProvider(key); File path = getApplicationContext().getDir("documentstores", MODE_PRIVATE); DocumentStore ds = DocumentStore.getInstance(new File(path, "my_documentstore"), keyProvider);
Note: The key must be 32 bytes (256-bit key).
"testAKeyPasswordtestAKeyPassword"happens to meet this requirement whengetBytes()is called, which makes this example more readable.See the next section for a secure key provider, which generates and protects keys for you.
The SimpleKeyProvider does not provide proper management and storage of the key.
If this is required, use the AndroidKeyProvider. This class handles
generating a strong encryption key protected using the provided password. The
key data is encrytped and saved into the application's SharedPreferences. The
constructor requires the Android context, a user-provided password, and an
identifier to access the saved data in SharedPreferences.
Example:
KeyProvider keyProvider = new AndroidKeyProvider(context,
"ASecretPassword", "AnIdentifier");
DocumentStore ds = DocumentStore.getInstance(new File(path, "my_documentstore"), keyProvider);One example of an identifier might be if multiple users share the same device, the identifier can be used on a per user basis to store a key to their individual database.
Right now, a different key is stored under each identifier, so one cannot
use identifiers to allow different users access to the same encrypted
database -- each user would get a different encryption key, and would
not be able to decrypt the database. For this use case, currently
a custom implementation of KeyProvider is required.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SQLiteDatabase.loadLibs(this);
// Get a DocumentStore instance with encryption using
// application internal storage path and a key
File path = getApplicationContext().getDir("documentstores", MODE_PRIVATE);
KeyProvider keyProvider =
new SimpleKeyProvider("testAKeyPasswordtestAKeyPassword".getBytes());
DocumentStore ds = null;
try {
ds = DocumentStore.getInstance(new File(path, "my_documentstore"), keyProvider);
} catch (DocumentStoreNotOpenedException e) {
e.printStackTrace();
}
// ...Below we list known issues and gotchas.
If you use a version of SQLCipher lower than 3.2, you may see an exception saving documents. We've observed this on version 3.1. It seems to be to do with the column type IDs reported by SQLite/SQLCipher not matching the constants defined in SQLCipher's Java code.
The exception has the form:
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Unexpected type: 5
at java.util.concurrent.FutureTask.report(FutureTask.java:93)
at java.util.concurrent.FutureTask.get(FutureTask.java:163)
at com.cloudant.sync.datastore.BasicDatastore.createDocumentFromRevision(BasicDatastore.java:1824)
...
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.RuntimeException: Unexpected type: 5
at com.cloudant.sync.datastore.BasicDatastore.getFullRevisionFromCurrentCursor(BasicDatastore.java:1709)
at com.cloudant.sync.datastore.BasicDatastore.getDocumentInQueue(BasicDatastore.java:284)
at com.cloudant.sync.datastore.BasicDatastore.createDocument(BasicDatastore.java:681)
at com.cloudant.sync.datastore.BasicDatastore.access$1500(BasicDatastore.java:65)
at com.cloudant.sync.datastore.BasicDatastore$23.call(BasicDatastore.java:1816)
at com.cloudant.sync.datastore.BasicDatastore$23.call(BasicDatastore.java:1812)
at SQLQueueCallable.call(SQLQueueCallable.java:34)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
To fix this, make sure to download 3.2 or higher of SQLCipher's Android library code from https://www.zetetic.net/sqlcipher/open-source/. The Android documentation on the SQLCipher site links to 3.1 right now.
We use JCE library to encrypt the attachments before saving to disk. There should be no licencing concerns for using JCE.
Databases are automatically encrypted with SQLCipher. SQLCipher requires including its BSD-style license and copyright in your application and documentation. Therefore, if you use document store encryption in your application, please follow the instructions mentioned here.