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
const val ALGORITHM_AES = "AES"
private fun generatePassphrase(): ByteArray {
val keyGenerator = KeyGenerator.getInstance(ALGORITHM_AES)
keyGenerator.init(256)
return keyGenerator.generateKey().encoded
}
const val SHARED_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_NAME
and themasterKey
to create the EncryptedSharedPreference
const val SHARED_PREFS_NAME = "bu.edu.projectportal.key.shared_prefs"
private fun getSharedPrefs(context: Context): SharedPreferences {
val masterKey =
MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
return EncryptedSharedPreferences.create(
context,
SHARED_PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
private fun initializePassphrase(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
private fun getPassphrase(context: Context): ByteArray? {
val passphraseString = getSharedPrefs(context)
.getString(PREFS_KEY_PASSPHRASE, null)
return passphraseString?.toByteArray(Charsets.ISO_8859_1)
}
@Entity(tableName = "projects_list")
data class Project(
@PrimaryKey(autoGenerate = true)
val id: Int,
@ColumnInfo(name = "title")
var title: String,
@ColumnInfo(name = "desc")
var description: String,
@ColumnInfo(name = "authors")
var authors: String,
@ColumnInfo(name = "projectLinks")
var projectLinks: String,
@ColumnInfo(name = "isFav")
var isFav: Boolean = false,
@ColumnInfo(name = "keywords")
var keywords: String,
@ColumnInfo(name = "programmingLanguagesUsed")
var programmingLanguagesUsed: String
)
@Database(entities = [Project::class], version = 1)
abstract class ProjectDatabase : RoomDatabase() {
abstract fun projectDao(): ProjectDao
}
private fun createDatabase(): ProjectDatabase {
val passphrase =
getPassphrase(applicationContext) ?: initializePassphrase(applicationContext)
val factory = SupportFactory(passphrase)
return Room.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>.db
Output: ![[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]]