This sample project is used to Demonstrate On-Device SQLLite Encryption using SQLCipher and Room Database
This document covers
- Using Cryptography dependencies to generate and store encyrption keys in EncryptedSharedPreferences
- Encrypting SQLite Database with SQL Cipher
- Verifying ondevice database is encrypted
- In App Level
build.gradle, add below dependencies
dependencies{
// Dependencies creating ViewModels and LiveData
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'// Dependencies for Room Database
def roomVersion ="2.5.0"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
annotationProcessor("androidx.room:room-compiler:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
// Dependency for SQL Cipher
implementation 'net.zetetic:android-database-sqlcipher:4.5.3'// Dependencies for Key Generation and EncryptedSharedPreferences
implementation "androidx.security:security-crypto:1.0.0"
implementation "androidx.security:security-crypto-ktx:1.1.0-alpha05"
}- We are using AES-256 Encryption for generating the key
constvalALGORITHM_AES="AES"privatefungeneratePassphrase(): ByteArray {
val keyGenerator =KeyGenerator.getInstance(ALGORITHM_AES)
keyGenerator.init(256)
return keyGenerator.generateKey().encoded
}constvalSHARED_PREFS_NAME="bu.edu.projectportal.key.shared_prefs"val masterKey =MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()EncryptedSharedPreferences.create(
context,
SHARED_PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)- We use the unique
SHARED_PREFS_NAMEand themasterKeyto create the EncryptedSharedPreference
constvalSHARED_PREFS_NAME="bu.edu.projectportal.key.shared_prefs"privatefungetSharedPrefs(context:Context): SharedPreferences {
val masterKey =MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
returnEncryptedSharedPreferences.create(
context,
SHARED_PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
privatefuninitializePassphrase(context:Context): ByteArray {
val passphrase = generatePassphrase() // returns the AES-256 bit key created earlier (check step 2)// getSharedPrefs is defined in step 3
getSharedPrefs(context).edit(commit =true) {
putString(PREFS_KEY_PASSPHRASE, passphrase.toString(Charsets.ISO_8859_1))
}
return passphrase
}- We use the passphrase key from the EncryptedSharedPreference to encrypt our SQLite Database
privatefungetPassphrase(context:Context): ByteArray? {
val passphraseString = getSharedPrefs(context)
.getString(PREFS_KEY_PASSPHRASE, null)
return passphraseString?.toByteArray(Charsets.ISO_8859_1)
}@Entity(tableName ="projects_list")
data classProject(
@PrimaryKey(autoGenerate =true)
valid:Int,
@ColumnInfo(name ="title")
vartitle:String,
@ColumnInfo(name ="desc")
vardescription:String,
@ColumnInfo(name ="authors")
varauthors:String,
@ColumnInfo(name ="projectLinks")
varprojectLinks:String,
@ColumnInfo(name ="isFav")
varisFav:Boolean = false,
@ColumnInfo(name ="keywords")
varkeywords:String,
@ColumnInfo(name ="programmingLanguagesUsed")
varprogrammingLanguagesUsed:String
)@Database(entities = [Project::class], version =1)
abstractclassProjectDatabase : RoomDatabase() {
abstractfunprojectDao(): ProjectDao
}privatefuncreateDatabase(): ProjectDatabase {
val passphrase =
getPassphrase(applicationContext) ?: initializePassphrase(applicationContext)
val factory =SupportFactory(passphrase)
returnRoom.databaseBuilder(
applicationContext,
ProjectDatabase::class.java,
DATABASE_NAME
)
.openHelperFactory(factory)
.fallbackToDestructiveMigration()
.build()
}In the above code,
- We first try to check if the key already exists in SharedPreferences, if it doesn't exist, we create a new Key and store it
val passphrase = getPassphrase(applicationContext) ?: initializePassphrase(applicationContext) - Create a Support factory instance from SQL Cipher using the passphrase
val factory = SupportFactory(passphrase) - Create Room Database, using Room Database Builder and the SupportFactory instance
Room.databaseBuilder(
applicationContext,
ProjectDatabase::class.java,
DATABASE_NAME
)
.openHelperFactory(factory)
.fallbackToDestructiveMigration()
.build()
}
- We have created an Encrypted SQLite Database and can implement methods to perform basic CRUD Operations as per Room Database Documentation.
- There are no additional changes that are needed to be done for implementing CRUD Operations.
- To verify if the database is encrypted or not download the sqlite database to your locally.
- In Android Studio, Go to Device File Explorer -> Data -> Data -> <APP_PACKAGE_NAME> -> databases
- Right Click on the db file and click Save As to download it to your local machine.
![[Pasted image 20230311015002.png | 500]]
- Once the database file is downloaded to your local machine, navigate to the downloaded folder through terminal and run below command
hexdump -C <sqlite_database>.dbOutput: ![[Pasted image 20230311015219.png | 800]]
- One of the cons to using SQLCipher for Encrypting Room Database is that we can no longer access the contents of the database from the Database Inspector in Android Studio.
![[Pasted image 20230311014433.png]]