Sembast supports using a user-defined codec to encode/decode data when read/written to disk. It provides a way to support encryption. Encryption itself is not part of sembast but an example of a simple encryption algorithm (Salsa20 and SHA256 used from external packages) is provided in the test folder.
In pubspec.yaml
:
dependencies:
# Dependencies for encryption example
encrypt: '>=3.1.0'
crypto: '>=2.0.6'
// Initialize the encryption codec with a user password
var codec = getEncryptSembastCodec(password: '[your_user_password]');
// Open the database with the codec
Database db = await factory.openDatabase(dbPath, codec: codec);
// ...your database is ready to use
If you create multiple records, the content of the database will look like this where each record is encrypted:
"version":1,"sembast":1,"codec":"ZddmpxjrwUgJk7YnlC0lDhz7S2Iqcp8="}
Zdd+qwanmg1Qw6VkwnolQkWrRnctJMD9
Zdd+qwanmg5Qw6VkwnolQkWrQX0zNcLsqMobWlZppdFrayfLvTE2Q129UKIWbkdxrezXtzmQGajd+39xMhMe5w==
Zdd+qwanmg9Qw6VkwnolQkWrQXoxIpaiug==
Zdd+qwanmghQw6VkwnolQkWrQX0zNcL2otYFH0gmudNtdnXd+CYxUE29U6MbdkdypevetyCeAKjd7Xl1PwIc9y0ovYrPnatrpqeL
The header of the database will contain a signature encoded by the codec itself so that a database cannot be opened if the password is wrong.
Any other custom encryption/codec can be used as long as you provide a way to encode/decode a json encodable object (Map, List, num, String, bool and num) to/from a single line String.
The codec must be able to handle the conversion of a json encodable data to a single line string. (i.e. json being one).
If your codec requires calling a plugin or an asynchronous operation you have to create an
asynchronous codec. One solution is to subclass AsyncContentCodecBase
(which is a Codec<Object?, String>
but with
the synchronous encoding/decoding disabled) where you just have to implement:
decodeAsync
encodeAsync
/// My simple asynchronous codec.
class MyAsyncCodec extends AsyncContentCodecBase {
String _reverseString(String text) => text.split('').reversed.join();
@override
Future<Object?> decodeAsync(String encoded) async {
// Simple demo, just reverse the json asynchronously.
return jsonDecode(_reverseString(encoded));
}
@override
Future<String> encodeAsync(Object? input) async {
// Simple demo, just reverse the json asynchronously.
return _reverseString(jsonEncode(input));
}
}
You have to specify a unique signature (not private) when opening the database with an sync codec:
// Open the database
var db = await factory.openDatabase('my_encoded_db.db',
codec: SembastCodec(signature: 'my_codec', codec: MyAsyncCodec()));
It is unfortunately not possible to encrypt an existing database in place as a database format is tight to the codec
used. You have to create a new one and copy the data. You can use databaseMerge
to copy the data
in a single operation/transaction.
Below is an example of how to convert an existing non-encrypted database to an encrypted one:
// Encryption codec to use (to set as you wish)
SembastCodec? encryptionCodec;
// Existing db name, to convert and remove if it exists
var nonEncryptedDbName = 'my_database.db';
var encryptedDbName = 'my_database_encrypted.db';
late Database db;
// Check if non encrypted database exists
if (await factory.databaseExists(nonEncryptedDbName)) {
// This should only happen once but could be a lengthy operation
// Open the non encrypted database
var nonEncryptedDb = await factory.openDatabase(nonEncryptedDbName);
// Create a new encrypted database and copy the existing content
await factory.deleteDatabase(encryptedDbName);
db = await factory.openDatabase(encryptedDbName,
codec: encryptionCodec);
// Copy the content
await databaseMerge(db, sourceDatabase: nonEncryptedDb);
// Close and delete the non encrypted database
await nonEncryptedDb.close();
await factory.deleteDatabase(nonEncryptedDbName);
} else {
// Open the encrypted database
db = await factory.openDatabase(encryptedDbName,
codec: encryptionCodec);
}