-
Notifications
You must be signed in to change notification settings - Fork 606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQLCipher disk encryption for Android + iOS #907
base: master
Are you sure you want to change the base?
Changes from all commits
820db09
9c8de8f
6074631
e4832cc
82dd122
577d2e5
1799b96
43c643a
a9bdd9d
cb480db
78b8fae
3237c52
08bd625
7bb0341
91216e2
d68c04c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,5 @@ coverage/ | |
# docs/CHANGELOG.md | ||
# docs/README.md | ||
docs-build/ | ||
|
||
test*.db |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,8 @@ dependencies { | |
//noinspection GradleDynamicVersion | ||
implementation 'com.facebook.react:react-native:+' | ||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||
implementation "net.zetetic:android-database-sqlcipher:4.4.2" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like we'd want to branch these based on the user's desire to use SQLCipher or not. If not, I imagine we'd want to use the native implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I feel a little uncomfortable forcing this dependency. Can we easily make it so that it's optional? For example, there would be a separate .kt file which adds to I'm not super familiar with ins and outs of Android. @rozPierog ideas? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no clear way to create dependency that's conditional that I know of. To make it easier for maintenance there should be another module Disclaimer: I have no idea what I'm doing. I've never attempted stuff like this and I'm not familiar with pitfalls of modularization like that. @cjroth Can you give your input here? How would you handle this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Haha! Me neither, I don't know even know Kotlin :) I think I agree with both of your comments though - I'm not aware of any way to do optional dependencies, so separating into modules seems like the best option. I do think this is kind of a bummer for users that don't need to use SQLCipher since it adds one more step to setup, but maybe it's worth it. |
||
implementation "androidx.sqlite:sqlite:2.0.1" | ||
} | ||
repositories { | ||
mavenCentral() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,17 @@ package com.nozbe.watermelondb | |
|
||
import android.content.Context | ||
import android.database.Cursor | ||
import android.database.sqlite.SQLiteDatabase | ||
import net.sqlcipher.database.SQLiteDatabase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
import java.io.File | ||
|
||
class Database(private val name: String, private val context: Context) { | ||
class Database(private val name: String, private var password: String?, private val context: Context) { | ||
|
||
private val db: SQLiteDatabase by lazy { | ||
if (password.equals("")) { | ||
password = null | ||
} | ||
|
||
SQLiteDatabase.loadLibs(context) | ||
SQLiteDatabase.openOrCreateDatabase( | ||
// TODO: This SUCKS. Seems like Android doesn't like sqlite `?mode=memory&cache=shared` mode. To avoid random breakages, save the file to /tmp, but this is slow. | ||
// NOTE: This is because Android system SQLite is not compiled with SQLITE_USE_URI=1 | ||
|
@@ -18,6 +23,7 @@ class Database(private val name: String, private val context: Context) { | |
} else | ||
// On some systems there is some kind of lock on `/databases` folder ¯\_(ツ)_/¯ | ||
context.getDatabasePath("$name.db").path.replace("/databases", ""), | ||
password, | ||
null) | ||
} | ||
|
||
|
@@ -79,12 +85,14 @@ class Database(private val name: String, private val context: Context) { | |
private fun getAllTables(): ArrayList<String> { | ||
val allTables: ArrayList<String> = arrayListOf() | ||
rawQuery(Queries.select_tables).use { | ||
it.moveToFirst() | ||
val index = it.getColumnIndex("name") | ||
if (index > -1) { | ||
do { | ||
allTables.add(it.getString(index)) | ||
} while (it.moveToNext()) | ||
if (it.count > 0) { | ||
it.moveToFirst() | ||
val index = it.getColumnIndex("name") | ||
if (index > -1) { | ||
do { | ||
allTables.add(it.getString(index)) | ||
} while (it.moveToNext()) | ||
} | ||
Comment on lines
-82
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this necessary? |
||
} | ||
} | ||
return allTables | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,9 +8,11 @@ class Database { | |
|
||
private let fmdb: FMDatabase | ||
private let path: String | ||
private let password: String | ||
|
||
init(path: String) { | ||
init(path: String, password: String) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't password be optional? |
||
self.path = path | ||
self.password = password | ||
fmdb = FMDatabase(path: path) | ||
open() | ||
} | ||
|
@@ -32,6 +34,7 @@ class Database { | |
} | ||
|
||
func inTransaction(_ executeBlock: () throws -> Void) throws { | ||
fmdb.setKey(self.password) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me why it was necessary to call setKey every time and I worry about performance with this. I think we want to figure this out before merging. Originally I tried calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup, agreed, should be figured out before merge, I don't like this either |
||
guard fmdb.beginTransaction() else { throw fmdb.lastError() } | ||
|
||
do { | ||
|
@@ -44,17 +47,20 @@ class Database { | |
} | ||
|
||
func execute(_ query: SQL, _ args: QueryArgs = []) throws { | ||
fmdb.setKey(self.password) | ||
try fmdb.executeUpdate(query, values: args) | ||
} | ||
|
||
/// Executes multiple queries separated by `;` | ||
func executeStatements(_ queries: SQL) throws { | ||
fmdb.setKey(self.password) | ||
guard fmdb.executeStatements(queries) else { | ||
throw fmdb.lastError() | ||
} | ||
} | ||
|
||
func queryRaw(_ query: SQL, _ args: QueryArgs = []) throws -> AnyIterator<FMResultSet> { | ||
fmdb.setKey(self.password) | ||
let resultSet = try fmdb.executeQuery(query, values: args) | ||
|
||
return AnyIterator { | ||
|
@@ -69,6 +75,7 @@ class Database { | |
|
||
/// Use `select count(*) as count` | ||
func count(_ query: SQL, _ args: QueryArgs = []) throws -> Int { | ||
fmdb.setKey(self.password) | ||
let result = try fmdb.executeQuery(query, values: args) | ||
defer { result.close() } | ||
|
||
|
@@ -86,18 +93,21 @@ class Database { | |
var userVersion: Int { | ||
get { | ||
// swiftlint:disable:next force_try | ||
fmdb.setKey(self.password) | ||
let result = try! fmdb.executeQuery("pragma user_version", values: []) | ||
result.next() | ||
defer { result.close() } | ||
return result.long(forColumnIndex: 0) | ||
} | ||
set { | ||
fmdb.setKey(self.password) | ||
// swiftlint:disable:next force_try | ||
try! execute("pragma user_version = \(newValue)") | ||
} | ||
} | ||
|
||
func unsafeDestroyEverything() throws { | ||
fmdb.setKey(self.password) | ||
// NOTE: Deleting files by default because it seems simpler, more reliable | ||
// But sadly this won't work for in-memory (shared) databases | ||
if isInMemoryDatabase { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be required. Seems appropriate to me to add to docs-master/Advanced a new .md for how to set up encryption. Alternatively, just put this at the end and specify that this is optional