Skip to content

Instantly share code, notes, and snippets.

@simolus3
Last active January 15, 2021 09:08
Show Gist options
  • Save simolus3/5097bbd80ce59f9b957961fe851fd95a to your computer and use it in GitHub Desktop.
Save simolus3/5097bbd80ce59f9b957961fe851fd95a to your computer and use it in GitHub Desktop.
Template to use moor_ffi with SQLCipher
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;
}

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)

@davidmartos96
Copy link

Oops, I have just spotted a small bug. The runUpdate method does not call the delegate... 😓

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment