Call init
before using moor, for instance in your main
method.
You can now use an encrypted database by replacing VmDatabase(file)
with EncryptedVmDatabase(key, file)
import 'dart:async'; | |
import 'dart:ffi'; | |
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:moor/backends.dart'; | |
import 'package:moor/moor.dart'; | |
import 'package:moor_ffi/moor_ffi.dart'; | |
import 'package:moor_ffi/open_helper.dart'; | |
/// Tells `moor_ffi` to use `sqlcipher` instead of the regular `sqlite3`. | |
/// | |
/// This needs to be called before using `moor`, for instance in the `main` | |
/// method. | |
void init() { | |
const sharedLibraryName = 'libsqlcipher.so'; | |
open.overrideFor(OperatingSystem.android, () { | |
try { | |
return DynamicLibrary.open(sharedLibraryName); | |
} catch (_) { | |
// On some (especially old) Android devices, we somehow can't dlopen | |
// libraries shipped with the apk. We need to find the full path of the | |
// library (/data/data/<id>/lib/libsqlite3.so) and open that one. | |
// For details, see https://github.com/simolus3/moor/issues/420 | |
final appIdAsBytes = File('/proc/self/cmdline').readAsBytesSync(); | |
// app id ends with the first \0 character in here. | |
final endOfAppId = max(appIdAsBytes.indexOf(0), 0); | |
final appId = String.fromCharCodes(appIdAsBytes.sublist(0, endOfAppId)); | |
return DynamicLibrary.open('/data/data/$appId/lib/$sharedLibraryName'); | |
} | |
}); | |
open.overrideFor(OperatingSystem.iOS, () => DynamicLibrary.executable()); | |
} | |
class VmDatabaseEncrypted extends DelegatedDatabase { | |
/// Creates a database that will store its result in the [file], creating it | |
/// if it doesn't exist. | |
factory VmDatabaseEncrypted( | |
File file, { | |
String password = '', | |
bool logStatements = false, | |
}) { | |
final vmDatabase = VmDatabase(file, logStatements: logStatements); | |
return VmDatabaseEncrypted._(vmDatabase, password); | |
} | |
factory VmDatabaseEncrypted.memory({ | |
String password = '', | |
bool logStatements = false, | |
}) { | |
final vmDatabase = VmDatabase.memory(logStatements: logStatements); | |
return VmDatabaseEncrypted._(vmDatabase, password); | |
} | |
VmDatabaseEncrypted._( | |
VmDatabase vmDatabase, | |
String password, | |
) : super( | |
_VmEncryptedDelegate(vmDatabase.delegate, password), | |
logStatements: vmDatabase.logStatements, | |
isSequential: vmDatabase.isSequential, | |
); | |
} | |
class _VmEncryptedDelegate extends DatabaseDelegate { | |
final String password; | |
final DatabaseDelegate delegate; | |
_VmEncryptedDelegate( | |
this.delegate, | |
this.password, | |
); | |
@override | |
Future<void> open(QueryExecutorUser db) async { | |
await delegate.open(db); | |
final keyLiteral = const StringType().mapToSqlConstant(password); | |
await delegate.runCustom('PRAGMA KEY = $keyLiteral', const []); | |
return Future.value(); | |
} | |
@override | |
FutureOr<bool> get isOpen => delegate.isOpen; | |
@override | |
Future<void> runBatched(BatchedStatements statements) { | |
return delegate.runBatched(statements); | |
} | |
@override | |
Future<void> runCustom(String statement, List args) { | |
return delegate.runCustom(statement, args); | |
} | |
@override | |
Future<int> runInsert(String statement, List args) { | |
return delegate.runInsert(statement, args); | |
} | |
@override | |
Future<QueryResult> runSelect(String statement, List args) { | |
return delegate.runSelect(statement, args); | |
} | |
@override | |
Future<int> runUpdate(String statement, List args) { | |
return delegate.runUpdate(statement, args); | |
} | |
@override | |
TransactionDelegate get transactionDelegate => delegate.transactionDelegate; | |
@override | |
DbVersionDelegate get versionDelegate => delegate.versionDelegate; | |
} |
Good point, thank you David! I've changed the class.
Oops, I have just spotted a small bug. The runUpdate method does not call the delegate... 😓
Nice Gist Simon!
What do you think about changing the implementation of
EncryptedVmDatabase
to something like this? With the following there is no need to repeat all the internal logic in the delegate methods in case something changes in the future. This is what I use in a project that uses moor_ffi and SQLCipher.