SQLCipher provides encryption of SQLite database files. It encrypts database using AES-256 in CBC mode. SQLCipher supports many platform including android. Here is a basic tutorial for setting up and using SQLCipher in android application.
In this tutorial I’ll try to explain how can we secure our Contentprovider with SQLCipher. We need to change few things in our MyDatabase class in order to use SQLCipher. We have to import:
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteOpenHelper;
instead of
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper;
Method signatures of both libraries are same so no modification is required in function calls.
Now lets see the MyContentProvider class. Here again we need use SQLCipher libs instead of android’s SQLite libs. Import
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteQueryBuilder;
Instead of
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder;
There is a small difference between SQLite and SQLCipher’s db.getReadableDatabase and db.getWriteableDatabase functions though.
android’s SQLite functions receives no argument while SQLCipher’s functions take (String) password as an argument. This password is used to encrypt when writing and decrypt when reading from database. To put things together, MyContentProvider class will be look like this when using SQLCipher:
package sohail.aziz.mycontentprovider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteQueryBuilder; import android.net.Uri; import android.util.Log; public class MyContentProvider extends ContentProvider { private MyDatabase mydb; private static String dbPassword = "sohail"; @Override public boolean onCreate() { Context ctx = getContext(); mydb = new MyDatabase(ctx); return (mydb == null) ? false : true; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = mydb.getWritableDatabase(dbPassword); int token = MyContentDescriptor.URI_MATCHER.match(uri); int count=0; switch(token){ case MyContentDescriptor.Categories.CAT_PATH_TOKEN: count= db.delete(MyContentDescriptor.Categories.TABLE_NAME, selection, selectionArgs); break; case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: count= db.delete(MyContentDescriptor.Transactions.TABLE_NAME, selection, selectionArgs); break; } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { // returning self defined mime types // to be used by other applications if any final int match = MyContentDescriptor.URI_MATCHER.match(uri); switch (match) { case MyContentDescriptor.Categories.CAT_PATH_TOKEN: case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: return MyContentDescriptor.CONTENT_TYPE_DIR; case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN: case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN: return MyContentDescriptor.CONTENT_ITEM_TYPE; } return null; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d("sohail", "inside insert"); SQLiteDatabase db =mydb.getWritableDatabase(dbPassword); int token = MyContentDescriptor.URI_MATCHER.match(uri); switch (token) { case MyContentDescriptor.Categories.CAT_PATH_TOKEN: // uri is of // categories table Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString()); long id = db.insert(MyContentDescriptor.Categories.TABLE_NAME, null, values); // notifying change to content observers getContext().getContentResolver().notifyChange(uri, null); return MyContentDescriptor.Categories.CONTENT_URI.buildUpon() .appendPath(String.valueOf(id)).build(); case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: // uri is of // transaction // table Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString()); long idd = db.insert(MyContentDescriptor.Transactions.TABLE_NAME, null, values); getContext().getContentResolver().notifyChange(uri, null); return MyContentDescriptor.Transactions.CONTENT_URI.buildUpon() .appendPath(String.valueOf(idd)).build(); default: throw new UnsupportedOperationException("URI: " + uri + " not supported."); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d("sohail", "query called"); SQLiteDatabase db = mydb.getReadableDatabase(dbPassword); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); Cursor c; int token = MyContentDescriptor.URI_MATCHER.match(uri); switch (token) { case MyContentDescriptor.Categories.CAT_PATH_TOKEN: Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString()); queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME); c = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); return c; case MyContentDescriptor.Categories.CAT_PATH_FOR_ID_TOKEN: Log.d("sohail", "matched uri is CAT_PATH_TOKEN:" + uri.toString()); queryBuilder.setTables(MyContentDescriptor.Categories.TABLE_NAME); queryBuilder.appendWhere(MyContentDescriptor.Categories.Cols.cat_id + "=" + uri.getLastPathSegment()); c = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); return c; case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString()); queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME); c = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); return c; case MyContentDescriptor.Transactions.TRAN_PATH_FOR_ID_TOKEN: Log.d("sohail", "matched uri is TRAN_PATH_TOKEN:" + uri.toString()); queryBuilder.setTables(MyContentDescriptor.Transactions.TABLE_NAME); queryBuilder .appendWhere(MyContentDescriptor.Transactions.Cols.tran_id + "=" + uri.getLastPathSegment()); c = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); return c; default: Log.d("sohail", "no URI MATCHED"); return null; } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = mydb.getWritableDatabase(dbPassword); int token = MyContentDescriptor.URI_MATCHER.match(uri); int count=0; switch(token){ case MyContentDescriptor.Categories.CAT_PATH_TOKEN: count= db.update(MyContentDescriptor.Categories.TABLE_NAME,values, selection, selectionArgs); break; case MyContentDescriptor.Transactions.TRAN_PATH_TOKEN: count= db.update(MyContentDescriptor.Transactions.TABLE_NAME,values,selection, selectionArgs); break; } getContext().getContentResolver().notifyChange(uri, null); return count; } }
Please note that we use
mydb.getWritableDatabase(dbPassword); and mydb.getReadableDatabase(dbPassword);
With this slight change the database file is encrypted using password dbPassword. Please do configure the libs and assests as explained on SQLCipher site before running this code.