Android Database Encryption

How to encrypt android sqlite database?

Take a look to the SQLCipher project. It is an open source library that provides transparent, secure 256-bit AES encryption of SQLite database files.

The web site offers a tutorial for Android.

How can I encrypt the existing Room database with sqlcipher?

Encrypt each record of your database separately , you can use this package encrypt in a function that encrypt each record of your databsae.

Which database for android ensures security and small database file size?

You could perhaps consider implementing your own encryption/decryption and then only partial encryption of the actual sensitive data.

For example the demo code below utilises a basic dictionary (word/definition) with 280000 definitions (albeit duplicated definitions). This takes up 20.9mB without encryption and 36.6mB when encrypted.

Sample Image

  • mydb being the unencrypted version
  • mydbenc the encrypted version

  • There again the data actually being stored, say for longer word definitions can have quite an impact, with the same number of words and definitions but with noticeably longer definitions (in the case where one of the 14 definitions (each being repeated 20000 times) was increased then the size of the en-encrypted database grew by 4mB, the encrypted database also grew by about 4mB). (the demo uses the larger DB)

    • Sample Image

So the encrypted size of your database would be about 20Mb for your 150,000 rows.

The size would also be impacted by the encryption method. In general the weaker the encryption method, less the overhead but with a lower factor of security.

To overcome the problem of searching, the example App decrypts the data into a temporary table when it is started. This does take just under a minute, which could be unacceptable.

  • part of an encrypted string would not equate to that part encrypted on it's own and hence the issue with performing searches.

The example code consists of two Database Helpers one for the unencrypted version used for comparison and the encrypted version. Both use the same table with 3 columns id (which isn't encrypted), word and definition the latter two are encrypted in the encrypted version.

The database, table and name columns are defined via constants in a class named DBConstants as per :-

public class DBConstants {

public static int FILTEROPTION_ANYWHERE = 0;
public static int FILTEROPTION_MUSTMATCH = 1;
public static int FILTEROPTION_STARTSWITH = 2;
public static int FILTEROPTION_ENDSWITH = 4;

public static final String DBName = "mydb";
public static final int DBVERSION = 1;
public static final String DECRYPTEXTENSION = "_decrypt";

public static class MainTable {

public static final String TBLNAME = "main";
public static final String COL_ID = BaseColumns._ID;
public static final String COl_WORD = "_word";
public static final String COL_DEFINITION = "_definition";

public static final String CRT_SQL = "CREATE TABLE IF NOT EXISTS " + TBLNAME +
"(" +
COL_ID + " INTEGER PRIMARY KEY," +
COl_WORD + " TEXT," +
COL_DEFINITION + " TEXT" +
")";
}

public static class DecrtyptedMainTable {
public static final String TBLNAME = MainTable.TBLNAME + DECRYPTEXTENSION;
public static final String CRT_SQL = "CREATE TEMP TABLE IF NOT EXISTS " + TBLNAME +
"(" +
MainTable.COL_ID + " INTEGER PRIMARY KEY," +
MainTable.COl_WORD + " TEXT, " +
MainTable.COL_DEFINITION + " TEXT " +
")";
public static final String CRTIDX_SQL = "CREATE INDEX IF NOT EXISTS " +
TBLNAME + "_index " +
" ON " + TBLNAME +
"(" + MainTable.COl_WORD + ")";
}
}

A Word class is used to allow Word objects to be extracted as per :-

public class Word {
private long id;
private String word;
private String definition;

public Word() {
this.id = -1L;
}

public Word(String word, String definition) {
this(-1L,word,definition);
}

public Word(Long id, String word, String definition) {
this.id = id;
this.word = word;
this.definition = definition;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getWord() {
return word;
}

public void setWord(String word) {
this.word = word;
}

public String getDefinition() {
return definition;
}

public void setDefinition(String definition) {
this.definition = definition;
}
}

The Database Helper DBHelperStandard.java for the non-encrypted database (exists purely for comparison purposes) is :-

public class DBHelperStandard extends SQLiteOpenHelper {

SQLiteDatabase db;

public DBHelperStandard(Context context) {
super(context, DBConstants.DBName, null, DBConstants.DBVERSION);
db = this.getWritableDatabase();
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DBConstants.MainTable.CRT_SQL);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

public long insertWord(String word, String definition) {
ContentValues cv = new ContentValues();
cv.put(DBConstants.MainTable.COl_WORD,word);
cv.put(DBConstants.MainTable.COL_DEFINITION,definition);
return db.insert(DBConstants.MainTable.TBLNAME,null,cv);
}

public long insertWord(Word word) {
return insertWord(word.getWord(),word.getDefinition());
}

public int deleteWord(long id) {
String whereclause = DBConstants.MainTable.COL_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return db.delete(DBConstants.MainTable.TBLNAME,whereclause,whereargs);
}

public int deleteWord(Word word) {
return deleteWord(word.getId());
}

public int updateWord(long id, String word, String defintion) {

ContentValues cv = new ContentValues();
if (word != null && word.length() > 0) {
cv.put(DBConstants.MainTable.COl_WORD,word);
}
if (defintion != null && defintion.length() > 0) {
cv.put(DBConstants.MainTable.COL_DEFINITION,defintion);
}
if (cv.size() < 1) return 0;
String whereclause = DBConstants.MainTable.COL_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return db.update(DBConstants.MainTable.TBLNAME,cv,whereclause,whereargs);
}

public int updateWord(Word word) {
return updateWord(word.getId(),word.getWord(),word.getDefinition());
}

public List<Word> getWords(String wordfilter, int filterOption, Integer limit) {
ArrayList<Word> rv = new ArrayList<>();
String whereclause = DBConstants.MainTable.COl_WORD + " LIKE ?";
StringBuilder sb = new StringBuilder();
switch (filterOption) {
case 0:
sb.append("%").append(wordfilter).append("%");
break;
case 1:
sb.append(wordfilter);
break;
case 2:
sb.append(wordfilter).append("%");
break;
case 4:
sb.append("%").append(wordfilter);
}
String[] whereargs = new String[]{sb.toString()};
if (wordfilter == null) {
whereclause = null;
whereargs = null;
}
String limitclause = null;
if (limit != null) {
limitclause = String.valueOf(limit);
}
Cursor csr = db.query(
DBConstants.MainTable.TBLNAME,
null,
whereclause,
whereargs,
null,
null,
DBConstants.MainTable.COl_WORD,
limitclause
);
while (csr.moveToNext()) {
rv.add(new Word(
csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)),
csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),
csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION))
));
}
return rv;
}
}

The Database Helper DBHelperEncrypted.java for the encrypted database is :-

public class DBHelperEncrypted extends SQLiteOpenHelper {

private String secretKey;
private String ivpParemeter;

SQLiteDatabase db;
public DBHelperEncrypted(Context context, String secretKey, String ivpParamter) {
super(context, DBConstants.DBName + DBConstants.DECRYPTEXTENSION, null, DBConstants.DBVERSION);
this.secretKey = secretKey;
this.ivpParemeter = ivpParamter;
db = this.getWritableDatabase();
}

@Override
public void onCreate(SQLiteDatabase db) { db.execSQL(DBConstants.MainTable.CRT_SQL); }

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

public long insertWord(String word, String definition) {
ContentValues cv = new ContentValues();
cv.put(DBConstants.MainTable.COl_WORD,EncryptDecrypt.encrypt(word,secretKey,ivpParemeter));
cv.put(DBConstants.MainTable.COL_DEFINITION,EncryptDecrypt.encrypt(definition,secretKey,ivpParemeter));
return db.insert(DBConstants.MainTable.TBLNAME,null,cv);
}

public long insertWord(Word word) {
return insertWord(word.getWord(),word.getDefinition());
}

public int deleteWord(long id) {
String whereclause = DBConstants.MainTable.COL_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return db.delete(DBConstants.MainTable.TBLNAME,whereclause,whereargs);
}

public int deleteWord(Word word) {
return deleteWord(word.getId());
}

public int updateWord(long id, String word, String defintion) {

ContentValues cv = new ContentValues();
if (word != null && word.length() > 0) {
cv.put(DBConstants.MainTable.COl_WORD,EncryptDecrypt.encrypt(word,secretKey,ivpParemeter));
}
if (defintion != null && defintion.length() > 0) {
cv.put(DBConstants.MainTable.COL_DEFINITION,EncryptDecrypt.encrypt(defintion,secretKey,ivpParemeter));
}
if (cv.size() < 1) return 0;
String whereclause = DBConstants.MainTable.COL_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return db.update(DBConstants.MainTable.TBLNAME,cv,whereclause,whereargs);
}

public int updateWord(Word word) {
return updateWord(word.getId(),word.getWord(),word.getDefinition());
}

public List<Word> getWords(String wordfilter, int filterOption, Integer limit) {
ArrayList<Word> rv = new ArrayList<>();
String whereclause = DBConstants.MainTable.COl_WORD + " LIKE ?";
StringBuilder sb = new StringBuilder();
switch (filterOption) {
case 0:
sb.append("%").append(wordfilter).append("%");
break;
case 1:
sb.append(wordfilter);
break;
case 2:
sb.append(wordfilter).append("%");
break;
case 4:
sb.append("%").append(wordfilter);
}
String[] whereargs = new String[]{sb.toString()};
String limitclause = null;
if (limit != null) {
limitclause = String.valueOf(limit);
}
Cursor csr = db.query(
DBConstants.DecrtyptedMainTable.TBLNAME,
null,
whereclause,
whereargs,
null,
null,
DBConstants.MainTable.COl_WORD,
limitclause
);
while (csr.moveToNext()) {
rv.add(
new Word(
csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)),
csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),
csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION))
)
);
}
return rv;
}

public void buildDecrypted(boolean create_index) {
db.execSQL(DBConstants.DecrtyptedMainTable.CRT_SQL);
Cursor csr = db.query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null);
ContentValues cv = new ContentValues();
while (csr.moveToNext()) {
cv.clear();
cv.put(DBConstants.MainTable.COL_ID,csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)));
cv.put(DBConstants.MainTable.COl_WORD,
EncryptDecrypt.decrypt(csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),secretKey,ivpParemeter));
cv.put(DBConstants.MainTable.COL_DEFINITION,
EncryptDecrypt.decrypt(csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION)),secretKey,ivpParemeter));
db.insert(DBConstants.DecrtyptedMainTable.TBLNAME,null,cv);
}
csr.close();
if (create_index) {
db.execSQL(DBConstants.DecrtyptedMainTable.CRTIDX_SQL);
}
}
}
  • The main difference is that the (probably not to be utilised as it would appear that the database is shipped) insert,and update methods encrypt the data and also the inclusion of a method buildDecrypted which creates a temporary table from the encrypted table. The temporary table being used for searching and extracting the data.

    • being a temporary table it will be deleted when the database is closed. Typically you would only close the database when the App finishes.

The encryption and decryption is handled by a class EncryotDecrypt as per EncryptDecrypt.java :-

public class EncryptDecrypt {
public static Cipher cipher;

/**
* Encryption, irrespective of the USER type, noting that this should
* only be used in conjunction with an EncryptDecrypt instance created
* using the 2nd/extended constructor
*
* @param toEncrypt The string to be encrypted
* @return The encrypted data as a string
*/
public static String encrypt(String toEncrypt, String secretKey, String ivParameterSpec) {
byte[] encrypted;
try {
if (cipher == null) {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
if (secretKey.length() < 16) {
secretKey = (secretKey + " ").substring(0,16);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(),"AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,new IvParameterSpec(ivParameterSpec.getBytes()));
encrypted = cipher.doFinal(toEncrypt.getBytes());
} catch (Exception e) {
e.printStackTrace();
return null;
}
return Base64.encodeToString(encrypted,Base64.DEFAULT);
}

/**
* Decrypt an encrypted string
* @param toDecrypt The encrypted string to be decrypted
* @return The decrypted string
*/
public static String decrypt(String toDecrypt, String secretKey, String ivParameterSpec) {
byte[] decrypted;
try {
if (cipher == null) {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
if (secretKey.length() < 16) {
secretKey = (secretKey + " ").substring(0,16);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(),"AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,new IvParameterSpec(ivParameterSpec.getBytes()));
decrypted = cipher.doFinal(Base64.decode(toDecrypt,Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
return null;
}
return new String(decrypted);
}
}
  • Note I'm no expert by any means, but I do believe that you could implement less/more secure encryption methods relatively easily.

Last, putting it all together for this demo, is MainActivity.java :-

public class MainActivity extends AppCompatActivity {

public static final String SK = "mysecretkey";
public static final String SALT = "124567890ABCDEFG";
DBHelperEncrypted mDBE;
DBHelperStandard mDBS;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBE = new DBHelperEncrypted(this,SK,SALT);
mDBS = new DBHelperStandard(this);

//Stage 1 - Build the demo databases
ArrayList<Word> wordsanddefinitions = new ArrayList<>();
for (int i=0; i < 20000; i ++) {
wordsanddefinitions.add(new Word("Apple","Something that falls on peoples heads that causes them to discover gravity."));
wordsanddefinitions.add(new Word("Bachelor","An unmarried man."));
wordsanddefinitions.add(new Word("Bachelor","A person who has been awarded a bachelor's degree."));
wordsanddefinitions.add(new Word("Bachelor","A fur seal, especially a young male, kept from the breeding grounds by the older males."));
wordsanddefinitions.add(new Word("Cat","A small domesticated carnivore, Felis domestica or F. catus, bred in a number of varieties."));
wordsanddefinitions.add(new Word("Dog","A domesticated canid, Canis familiaris, bred in many varieties."));
wordsanddefinitions.add(new Word("Eddy","A current at variance with the main current in a stream of liquid or gas, especially one having a rotary or whirling motion."));
wordsanddefinitions.add(new Word("Eddy","A small whirlpool."));
wordsanddefinitions.add(new Word("Eddy","Any similar current, as of air, dust, or fog."));
wordsanddefinitions.add(new Word("Eddy","A current or trend, as of opinion or events, running counter to the main current."));
wordsanddefinitions.add(new Word("Orange","A colour bewteen Red and Yellow."));
wordsanddefinitions.add(new Word("Orange","a globose, reddish-yellow, bitter or sweet, edible citrus fruit."));
wordsanddefinitions.add(new Word("Orange","any white-flowered, evergreen citrus trees of the genus Citrus, bearing this fruit, " +
"as C. aurantium (bitter orange, Seville orange, or sour orange) " +
"and C. sinensis (sweet orange), cultivated in warm countries."));
wordsanddefinitions.add(new Word("Orange","Any of several other citrus trees, as the trifoliate orange."));
}
Log.d("STAGE1","Starting to build the Standard (non-encrypted) DB with " + String.valueOf(wordsanddefinitions.size()) + " definitions");
mDBS.getWritableDatabase().beginTransaction();
for (Word w: wordsanddefinitions ) {
mDBS.insertWord(w);
}
mDBS.getWritableDatabase().setTransactionSuccessful();
mDBS.getWritableDatabase().endTransaction();

Log.d("STAGE2","Starting to build the Encrypted DB with " + String.valueOf(wordsanddefinitions.size()) + " definitions");
mDBE.getWritableDatabase().beginTransaction();
for (Word w: wordsanddefinitions) {
mDBE.insertWord(w);
}

// Decrypt the encrypted table as a TEMPORARY table
Log.d("STAGE 3","Bulding the temporary unencrypted table");
mDBE.buildDecrypted(true); // Build with index on word column
mDBE.getWritableDatabase().setTransactionSuccessful();
mDBE.getWritableDatabase().endTransaction();

// Database now usable
Log.d("STAGE4","Extracting data (all words that include ap in the word) from the Standard DB");
List<Word> extracted_s = mDBS.getWords("ap",DBConstants.FILTEROPTION_ANYWHERE,10);
for (Word w: extracted_s) {
Log.d("WORD_STANDARD",w.getWord() + " " + w.getDefinition());
}

Log.d("STAGE5","Extracting data (all words that include ap in the word) from the Encrypted DB");
List<Word> extracted_e = mDBE.getWords("ap",DBConstants.FILTEROPTION_ANYWHERE,10);
for (Word w: extracted_e) {
Log.d("WORD_ENCRYPTED",w.getWord() + " " + w.getDefinition());
}

Log.d("STAGE5","Extracting demo data from standard and from encrypted without decryption");
Cursor csr = mDBE.getWritableDatabase().query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null,"10");
DatabaseUtils.dumpCursor(csr);
csr = mDBS.getWritableDatabase().query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null,"10");
DatabaseUtils.dumpCursor(csr);
mDBS.close();
mDBE.close();
}
}

This :-

  1. (Stage 1) Creates an ArrayList of Word objects based upon 14 core Word definitions repeated 20000 times (i.e. 280000 objects).
  2. (Stage 1) Uses the insert method to build the non-encrypted database.
  3. (Stage 2) Uses the insert method (which encrypts the data) for the encrypted database.

  4. (Stage 3) Builds the temporary decrypted table and also an index (the index can be skipped by using mDBE.buildDecrypted(false); (it doesn't appear to have much of an imapct, especially as it's built after the inserts)).

  5. (Stage 4) extracts some data using the getWords method (filtered) from the non-encrypted dataabse and writes the extracted data to the log.

  6. (Stage 5) extracts some data using the getWords method (filtered) from the encrypted database (from the decrypted temporary table) and writes the extracted data to the log.

    • Output from Stage 4 and Stage 5 should match.
  7. Extracts the first 10 rows (2 shown for brevity) from the encrypted database's persisted table (i.e. the encrypted data, not the decrypted data).

  8. Extracts the first 10 rows (2 shown fro berevity) from the non-encrypted database and dumps the Cursor to the log.

    • Output from 7 and 8 shows whats in the persisted database.

Result

6-05 13:51:36.932  D/STAGE1: Starting to build the Standard (non-encrypted) DB with 280000 definitions
06-05 13:51:59.274 D/STAGE2: Starting to build the Encrypted DB with 280000 definitions
06-05 13:52:45.327 D/STAGE 3: Bulding the temporary unencrypted table
06-05 13:52:45.350 W/CursorWindow: Window is full: requested allocation 111 bytes, free space 98 bytes, window size 2097152 bytes
.........
06-05 13:53:35.024 D/STAGE4: Extracting data (all words that include ap in the word) from the Standard DB
06-05 13:53:35.346 D/WORD_STANDARD: Apple Something that falls on peoples heads that causes them to discover gravity.
..........
06-05 13:53:35.346 D/STAGE5: Extracting data (all words that include ap in the word) from the Encrypted DB
06-05 13:53:35.346 D/WORD_ENCRYPTED: Apple Something that falls on peoples heads that causes them to discover gravity.
..........
06-05 13:53:35.347 D/STAGE5: Extracting demo data from standard and from encrypted without decryption
06-05 13:53:35.347 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d05c965
06-05 13:53:35.347 I/System.out: 0 {
06-05 13:53:35.347 I/System.out: _id=1
06-05 13:53:35.347 I/System.out: _word=3mqQlZl55WNjeZhALFQU7w==
06-05 13:53:35.347 I/System.out: _definition=s9Waa2HLUS2fy8q1uC9/MEKogmImu6m9MIpi9wasD9D3Zom6+/u40DnFfP6zXOyI8IgnQOKcWfQ8
06-05 13:53:35.347 I/System.out: G3uJN9a/YHMoQdEQMDMEEdSE2kWyJrc=
06-05 13:53:35.347 I/System.out: }
06-05 13:53:35.347 I/System.out: 1 {
06-05 13:53:35.347 I/System.out: _id=2
06-05 13:53:35.347 I/System.out: _word=LtLlycoBd9fm3eYF9aoItg==
06-05 13:53:35.347 I/System.out: _definition=B1XJJm0eC8wPi3xGg4XgJtvIS3xL7bjixNhVAVq1UwQ=
06-05 13:53:35.347 I/System.out: }

06-05 13:53:35.348 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@7f1b63a
06-05 13:53:35.348 I/System.out: 0 {
06-05 13:53:35.348 I/System.out: _id=1
06-05 13:53:35.348 I/System.out: _word=Apple
06-05 13:53:35.348 I/System.out: _definition=Something that falls on peoples heads that causes them to discover gravity.
06-05 13:53:35.348 I/System.out: }
06-05 13:53:35.348 I/System.out: 1 {
06-05 13:53:35.348 I/System.out: _id=2
06-05 13:53:35.348 I/System.out: _word=Bachelor
06-05 13:53:35.348 I/System.out: _definition=An unmarried man.
06-05 13:53:35.348 I/System.out: }
06-05 13:53:35.349 I/System.out: <<<<<

encrypt sqlite database Android:

Step #0: Add the code to your UI to prompt the user to enter a passphrase.

Step #1: Download the SQLCipher for Android ZIP file.

Step #2: UnZIP the ZIP file and navigate to the directory that has an assets/ and a libs/ folder.

Step #3: Copy the contents of the assets/ directory into your project's assets/ directory.

Step #4: Copy the contents of the libs/ directory into your project's libs/ directory. Gradle/Android Studio users will also need to add a line to the top-level dependencies closure loading up the contents of libs/, if you do not have one already.

Step #5: Replace all relevant android.database.* and android.database.sqlite.* imports with their SQLCipher for Android equivalents. If you are using an IDE that can help you resolve missing imports (e.g., Ctrl-Shift-O in Eclipse), the easiest thing to do is to get rid of all existing android.database.* and android.database.sqlite.* imports and let the IDE help resolve them. Choose the net.sqlcipher imports when given the choice.

Step #6: You will now have compiler errors on a few methods where you open a database (e.g., getReadableDatabase() on SQLiteOpenHelper), where you now need to pass in the passphrase you collected from the user in Step #0.

This will work for new apps starting up with new databases. There is additional work involved to upgrade an existing app with existing users, if you want to allow those users to switch to an encrypted database.

Android: Room: no encryption and security?

Room by default store data in the app's internal storage which any root user can access.

if you need some security you need to use encryption lib like this cwac-saferoom.

Encrypt Existing Database with SQLCipher in Android

I solved, I write my solution as future reference. I was simply getting the wrong path

public void encryptDataBase(String passphrase) throws IOException {

File originalFile = context.getDatabasePath(DB_NAME);

File newFile = File.createTempFile("sqlcipherutils", "tmp", context.getCacheDir());

SQLiteDatabase existing_db = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, "", null, SQLiteDatabase.OPEN_READWRITE);

existing_db.rawExecSQL("ATTACH DATABASE '" + newFile.getPath() + "' AS encrypted KEY '" + passphrase + "';");
existing_db.rawExecSQL("SELECT sqlcipher_export('encrypted');");
existing_db.rawExecSQL("DETACH DATABASE encrypted;");

existing_db.close();

originalFile.delete();

newFile.renameTo(originalFile);

}

How to protect my encryption key in Android?

You can use Andriod Keystore to encrypt your SQLCipher password.

I had the same issue while ago, where SQLCipher was used to secure data, but password itself was not. This allowed a security flaw where a simple decompilation would reveal the password as it was in the form of string constant.

My solution was:

  • Generate a random number when app starts at first. (You can change this behaviour for whatever suits you)
  • Encrypt this number using Android Keystore.
  • The original form of the number is gone once its encrypted.
  • Save this in Prefs.
  • Now, whenever SQLCipher needs password, it will decrypt it and use it.
  • Since Android Keystore is providing keys at runtime, and keys are strictly app specific, it will be hard to break this database.
  • Although everything can be broken but this approach will make it a lot harder for the attacker to retrieve data from DB, or DB password.

Here is a sample project I made which also has a SQLCipher use case same as yours.

Encryption Helper for Encrypting Passwords

Use case for SQLCipher

Note that the term you are using as encryption key is used as password/number for DB in above discussion.



Related Topics



Leave a reply



Submit