From a799ca55bcb8833c1a2e89078df0460229ef5053 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 16 Jun 2024 20:58:54 +0600 Subject: [PATCH] chore: add encrypted text column support --- lib/main.dart | 2 + lib/models/database/database.dart | 6 ++- .../typeconverters/encrypted_text.dart | 39 +++++++++++++++++ lib/services/kv_store/encrypted_kv_store.dart | 43 +++++++++++++++++++ lib/services/kv_store/kv_store.dart | 35 +++++++++++++++ pubspec.lock | 24 +++++++++++ pubspec.yaml | 1 + 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 lib/models/database/typeconverters/encrypted_text.dart create mode 100644 lib/services/kv_store/encrypted_kv_store.dart diff --git a/lib/main.dart b/lib/main.dart index bdccadd4..09db495c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,6 +27,7 @@ import 'package:spotube/provider/palette_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/cli/cli.dart'; +import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/wm_tools/wm_tools.dart'; @@ -76,6 +77,7 @@ Future main(List rawArgs) async { } await KVStoreService.initialize(); + await EncryptedKvStoreService.initialize(); final hiveCacheDir = kIsWeb ? null : (await getApplicationSupportDirectory()).path; diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index ad09933d..ac0223fd 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -4,11 +4,14 @@ import 'dart:convert'; import 'dart:io'; import 'package:drift/drift.dart'; +import 'package:encrypt/encrypt.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; +import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:flutter/material.dart' hide Table; +import 'package:flutter/material.dart' hide Table, Key; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:drift/native.dart'; import 'package:sqlite3/sqlite3.dart'; @@ -24,6 +27,7 @@ part 'tables/blacklist.dart'; part 'typeconverters/color.dart'; part 'typeconverters/locale.dart'; part 'typeconverters/string_list.dart'; +part 'typeconverters/encrypted_text.dart'; @DriftDatabase( tables: [ diff --git a/lib/models/database/typeconverters/encrypted_text.dart b/lib/models/database/typeconverters/encrypted_text.dart new file mode 100644 index 00000000..27921788 --- /dev/null +++ b/lib/models/database/typeconverters/encrypted_text.dart @@ -0,0 +1,39 @@ +part of '../database.dart'; + +class DecryptedText { + final String value; + const DecryptedText(this.value); + + static Encrypter? _encrypter; + + factory DecryptedText.decrypted(String value) { + _encrypter ??= Encrypter( + Salsa20( + Key.fromUtf8(EncryptedKvStoreService.encryptionKeySync), + ), + ); + + return DecryptedText( + _encrypter!.decrypt( + Encrypted.fromBase64(value), + iv: KVStoreService.ivKey, + ), + ); + } + + String encrypt() { + return _encrypter!.encrypt(value, iv: KVStoreService.ivKey).base64; + } +} + +class EncryptedTextConverter extends TypeConverter { + @override + DecryptedText fromSql(String fromDb) { + return DecryptedText.decrypted(fromDb); + } + + @override + String toSql(DecryptedText value) { + return value.encrypt(); + } +} diff --git a/lib/services/kv_store/encrypted_kv_store.dart b/lib/services/kv_store/encrypted_kv_store.dart new file mode 100644 index 00000000..d8f69690 --- /dev/null +++ b/lib/services/kv_store/encrypted_kv_store.dart @@ -0,0 +1,43 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:spotube/services/kv_store/kv_store.dart'; +import 'package:uuid/uuid.dart'; + +abstract class EncryptedKvStoreService { + static const _storage = FlutterSecureStorage( + aOptions: AndroidOptions( + encryptedSharedPreferences: true, + ), + ); + + static late final String _encryptionKeySync; + + static Future initialize() async { + _encryptionKeySync = await encryptionKey; + } + + static String get encryptionKeySync => _encryptionKeySync; + + static Future get encryptionKey async { + try { + final value = await _storage.read(key: 'encryption'); + final key = const Uuid().v4(); + + if (value == null) { + await setEncryptionKey(key); + return key; + } + + return value; + } catch (e) { + return KVStoreService.encryptionKey; + } + } + + static Future setEncryptionKey(String key) async { + try { + await _storage.write(key: 'encryption', value: key); + } catch (e) { + await KVStoreService.setEncryptionKey(key); + } + } +} diff --git a/lib/services/kv_store/kv_store.dart b/lib/services/kv_store/kv_store.dart index ae62a055..6b19c032 100644 --- a/lib/services/kv_store/kv_store.dart +++ b/lib/services/kv_store/kv_store.dart @@ -1,7 +1,9 @@ import 'dart:convert'; +import 'package:encrypt/encrypt.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/services/wm_tools/wm_tools.dart'; +import 'package:uuid/uuid.dart'; abstract class KVStoreService { static SharedPreferences? _sharedPreferences; @@ -43,4 +45,37 @@ abstract class KVStoreService { value.toJson(), ), ); + + static String get encryptionKey { + final value = sharedPreferences.getString('encryption'); + + final key = const Uuid().v4(); + if (value == null) { + setEncryptionKey(key); + return key; + } + + return value; + } + + static Future setEncryptionKey(String key) async { + await sharedPreferences.setString('encryption', key); + } + + static IV get ivKey { + final iv = sharedPreferences.getString('iv'); + final value = IV.fromSecureRandom(8); + + if (iv == null) { + setIVKey(value); + + return value; + } + + return IV.fromBase64(iv); + } + + static Future setIVKey(IV iv) async { + await sharedPreferences.setString('iv', iv.base64); + } } diff --git a/pubspec.lock b/pubspec.lock index c5871a2a..70b0655c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" + url: "https://pub.dev" + source: hosted + version: "1.5.3" async: dependency: "direct main" description: @@ -539,6 +547,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.13" + encrypt: + dependency: "direct main" + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" envied: dependency: "direct main" description: @@ -1670,6 +1686,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" pool: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ddace46e..a923f5a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -124,6 +124,7 @@ dependencies: drift: ^2.18.0 sqlite3_flutter_libs: ^0.5.23 sqlite3: ^2.4.3 + encrypt: ^5.0.3 dev_dependencies: build_runner: ^2.4.11