Saturday 21 May 2016

Secure Database in Android with Sqlcipher

"How to secure the user data?" is the common question for developer, especially when our application work with user's sensitive data: Address, phone number, email, password,...

There are many useful tips for a security app in Android can be found here
https://developer.android.com/training/articles/security-tips.html
These are the technics to avoid DIRECT ATTACK (The hacker user the security holes of the app itself)

But what about INDIRECT ATTACK: The virus does not go after your application directly. Instead it goes after to Android OS. The it can copy and exploit the data of other applications.

In Android, and also other mobile platforms, OS provide an OS security level for each app that called Sandbox (Internal storage only)
It guarantees an app cannot access to the data of others. So the data in internal storage of Android application(/data/data/...) is safe with with INDIRECT ATTACK.
It's true with default user privilege. But if the phone is rooted, user can access anything in it, the Sandbox barrier is not available in this case.

An indirect attacker + root user (rooted phone) can view all data in phone.

So how to protect the data even though the attacker can get it? The answer is encryption. Encryption add an layer of protection. The virus/attacker would see the garbled data.
There two kind of stored data in Android: normal file and database file.

  • Normal file: It's easy to apply encryption. Developer just chooses an suitable encryption that provide by platform API then encrypt/decrypt easily.
    This is the good tutorial about it
    http://www.developer.com/ws/android/encrypting-with-android-cryptography-api.html
  • Database file: We cannot just encrypt/decrypt the database file as above because there are many operations on database during application life cycle. It's will be a performance disaster.
Today, I will introduce a way - a library - to do encryption on Android database file. May be you here it before: Sqlcipher.
SQLCipher is an open source library that provides transparent, secure 256-bit AES encryption of SQLite database files.
SQLCipher has been adopted as a secure database solution by many commercial and open source products, making it one of the most popular encrypted database platforms for Mobile, Embedded, and Desktop applications
https://www.zetetic.net/sqlcipher/about/ 


SQLCipher provide transparent, secure 256-bit AES encryption of SQLite database files.

An very interesting feature of it is transparent. It means, with it, developer don't need to change the original database API to make it work. In Android, the source code with Database, Cursor, DatabaseHelper is still valid.

I do not waste your time anymore. Now, I'll introduce how to integrate SQLCipher to an Android project with Android Studio.
The tutorial for Eclipse can be found here https://www.zetetic.net/sqlcipher/sqlcipher-for-android/

1. Firstly, this is the location of the library https://bintray.com/developernotes/maven/net.zetetic%3Aandroid-database-sqlcipher/view. Choose a version, the select your build configuration. I choose Gradle for this example

2. Create an simple example to apply the library.I use the Login project template of Android.

3. After create project successfully, add the following line (from step 1) to gradle file of module app
compile 'net.zetetic:android-database-sqlcipher:3.4.0'
 4. In this sample we need save/retrieve account information to database to register and login. So we need a model class. It's called Account.
public class Account {
    public long mID;    public String mName;    public String mPassword;    public double mTime;
    public Account() {
    }

    public Account(long id, String name, String password, float time) {
        mID = id;        mName = name;        mPassword = password;        mTime = time;    }
}
5. As usual, we will create a database helper class to work with sqlite in Android
mport android.content.Context;import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;import net.sqlcipher.database.SQLiteOpenHelper;
/** * Created by sumpham on 5/19/16. */public class CipherDatabaseHelper extends SQLiteOpenHelper {

    public static final String TABLE_TARGET = "table_result";    public static final String COLUMN_ID = "_id";    public static final String COLUMN_NAME = "col_name";    public static final String COLUMN_PASSWORD = "col_password";    public static final String COLUMN_TIME = "col_time";    private static final String ACCOUNT_CREATE = "create table "            + TABLE_TARGET + "(" + COLUMN_ID            + " integer primary key autoincrement, "            + COLUMN_NAME + " text, "            + COLUMN_PASSWORD + " text, "            + COLUMN_TIME + " real);";
    private static final String DATABASE_NAME = "accounts.db";
    private static final int DATABASE_VERSION = 1;
    @Override    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(ACCOUNT_CREATE);    }

    @Override    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        Log.v(CipherDatabaseHelper.class.getName(),                "Upgrading database from version " + i + " to "                        + i1 + ", which will destroy all old data");        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_TARGET);        onCreate(sqLiteDatabase);    }

    public CipherDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);    }
}
Note that: we use "net.sqlcipher" instead  of "android.database.sqlite" of Android. The APIs are same between two package.

6. To easily work with database, I create a wrapper class to manipulate with Account table.
import android.content.ContentValues;import android.content.Context;import android.support.annotation.NonNull;
import net.sqlcipher.Cursor;import net.sqlcipher.SQLException;import net.sqlcipher.database.SQLiteDatabase;
import java.util.ArrayList;import java.util.List;
/** * Created by sumpham on 5/19/16. */public class AccountTable {
    private SQLiteDatabase mDatabase;    private CipherDatabaseHelper mDbHelper;
    /**     * Array of all columns in the table     */    private String[] mAllColumns = {
            CipherDatabaseHelper.COLUMN_ID,            CipherDatabaseHelper.COLUMN_NAME,            CipherDatabaseHelper.COLUMN_PASSWORD,            CipherDatabaseHelper.COLUMN_TIME    };
    public AccountTable(Context context) {
        mDbHelper = new CipherDatabaseHelper(context);    }

    /**     * Open the SQLite database     */    public void open() throws SQLException {
        mDatabase = mDbHelper.getWritableDatabase("my_secure_key");    }

    /**     * Close the SQLite database     */    public void close() {
        mDbHelper.close();    }

    public Account addAccount(@NonNull final Account result) {
        ContentValues values = new ContentValues();        values.put(CipherDatabaseHelper.COLUMN_NAME, result.mName);        values.put(CipherDatabaseHelper.COLUMN_PASSWORD, result.mPassword);        values.put(CipherDatabaseHelper.COLUMN_TIME, result.mTime);
        final long insertId = mDatabase.insert(CipherDatabaseHelper.TABLE_TARGET, null, values);
        Cursor cursor = mDatabase.query(CipherDatabaseHelper.TABLE_TARGET,                mAllColumns, CipherDatabaseHelper.COLUMN_ID + " = "                        + insertId, null, null, null, null);
        cursor.moveToFirst();
        Account resultInserted = cursorToAccount(cursor);        cursor.close();
        return resultInserted;    }

    public Account getAccountByName(String username) {
        Account rv = null;        List<Account> accounts = new ArrayList<>();        Cursor cursor = mDatabase.query(CipherDatabaseHelper.TABLE_TARGET, mAllColumns, "col_name = ?",                new String[]{username}, null, null, null, null);        if(null != cursor && cursor.getCount() > 0) {
            cursor.moveToFirst();           rv = cursorToAccount(cursor);            cursor.close();        }

        return rv;    }

    /**     * Take the cursor from a database query and create fill the result structure     *     * @param cursor Database cursor from sqlite query     * @return new result structure     */    private Account cursorToAccount(@NonNull final Cursor cursor) {
        Account result = new Account();        result.mID = cursor.getLong(0);        result.mName = cursor.getString(1);        result.mPassword = cursor.getString(2);        result.mTime = cursor.getFloat(3);        return result;    }


}
As named, The function "getAccountByName" to get and account from database by username and "addAccount" to add an account to database. And again, we use "net.sqlcipher" package.
We need password to open database: getWritableDatabase


7.   We finished with prepare helper classes for the demo. Let apply it to application work flow.

  • Go to LoginActivity.java, add following line to declare variable for AccountTable

AccountTable accountsTb; 

  •  Add a function to initialize sqlcipher database
    Please note that the call SQLiteDatabase.loadLibs(..) must be called before other actions to database.
private void initCipherDatabase() {
    SQLiteDatabase.loadLibs(this);    accountsTb = new AccountTable(this);    accountsTb.open();}

  •  Put this function to the end of onCreate function.
  • Go to UserLoginTask implementation to apply our database code. This class after done
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

    private final String mEmail;    private final String mPassword;
    UserLoginTask(String email, String password) {
        mEmail = email;        mPassword = password;    }

    @Override    protected Boolean doInBackground(Void... params) {
        // TODO: attempt authentication against a network service.
        try {
            // Simulate network access.            Thread.sleep(2000);        } catch (InterruptedException e) {
            return false;        }

        if(null != accountsTb) {
            Account account = accountsTb.getAccountByName(this.mEmail);            if (null != account) {
                return account.mPassword == this.mPassword;            } else {
                Account newAccount = new Account();                newAccount.mPassword = mPassword;                newAccount.mName = mEmail;                return accountsTb.addAccount(newAccount) != null;            }
        } else {
            return false;        }

    }

    @Override    protected void onPostExecute(final Boolean success) {
        mAuthTask = null;        showProgress(false);
        if (success) {
            Toast.makeText(LoginActivity.this, "Login/register success", Toast.LENGTH_SHORT).show();        } else {
            mPasswordView.setError(getString(R.string.error_incorrect_password));            mPasswordView.requestFocus();        }
    }

    @Override    protected void onCancelled() {
        mAuthTask = null;        showProgress(false);    }
}
The heart of this code is
if(null != accountsTb) {
    Account account = accountsTb.getAccountByName(this.mEmail);    if (null != account) {
        return account.mPassword.contentEquals(this.mPassword);
    } else {
        Account newAccount = new Account();        newAccount.mPassword = mPassword;        newAccount.mName = mEmail;        return accountsTb.addAccount(newAccount) != null;    }
} else {
    return false;}
We just search the account in database with current username. If it's exist, compare the saved password with current. Otherwise, add a new account to database.
  • Build and run code to see how it work.

8. To view the encrypted database by SQLCipher, you can download the SQLCipher version of SQLiteBrowser here http://sqlitebrowser.org/. You need the database password to open

Complete sample source here Source code

Conclusion

It's very easy to apply SQLCipher to your project. But if you're working in a project that the performance is the most important, you should be careful. Because the encryption will take time to do.
Here is the test result to consider
https://www.zetetic.net/blog/2011/5/7/sqlcipher-performance-and-sqlcipherspeed.html

This library is also available for iOS platform

No comments:

Post a Comment