- AppDatabase.open()/openForPush() kapseln Factory, Schluessel (Keystore), Klartext->SQLCipher-Migration (Backup/Verify/atomarer Tausch) und PRAGMAs - Android/Windows/Linux verschluesselt, iOS/macOS bewusst Klartext - 5 neue Tests mit echter SQLCipher-DLL; Windows-Praxistest mit echter DB erfolgreich (23 Tabellen verifiziert, E2EE intakt); Android UNGETESTET
7.6 KiB
SQLCipher-Migration der pyramid.sqlite – Umsetzungsplan (PC-Termin)
STATUS 2026-07-06: UMGESETZT (
lib/core/app_database.dart) und auf Windows mit Bernds echter 14-MB-DB praxisgetestet (Migration gelaufen, 23 Tabellen verifiziert, Login/E2EE danach intakt, Neustart öffnet die verschlüsselte DB,flutter testdeckt Migration/Fehlpfade mit echtem SQLCipher ab). Android bleibt UNGETESTET (Punkt 4 des Testplans unten: Migration auf Gerät, Push-Isolate + secure_storage) – erst danach Release und Uta. Abweichung vom Plan unten: Die Annahme „keine Desktop-Binaries" ist seit sqlcipher_flutter_libs 0.6.x überholt – das Plugin baut SQLCipher auf Windows/Linux alssqlite3.dll/libsqlite3.somit (im Build-Output verifiziert). Deshalb ist Phase 1 NICHT Android-only: Windows/Linux verschlüsseln ebenfalls, ein Test-Override ist unnötig. iOS/macOS bleiben Klartext (SQLCipher-Pod kollidiert mit firebase_messaging, kein Release-Ziel).
Für Bernd in einfach
Die Datenbank der App (Nachrichten, Zugangs-Token, Verschlüsselungs-Schlüssel) liegt heute unverschlüsselt auf dem Gerät – geschützt nur durch die Android-App-Sandbox. Ein Angreifer bräuchte Root oder physischen Zugriff mit Entwickler-Tools, um sie zu lesen. Dieser Plan verschlüsselt die Datei selbst. Der riskante Teil ist nicht die Verschlüsselung, sondern die einmalige Umwandlung bestehender Installationen (Utas Handy!) – deshalb: erst am PC mit Testdaten, dann eigenes Gerät, dann erst Release.
Offene Entscheidung (Frage in PROGRESS.md): Priorität vor/nach M2?
Ist-Zustand (verifiziert im Code)
| Stelle | Datei | Factory heute |
|---|---|---|
| Haupt-App Android/iOS | lib/core/matrix_client.dart:24 |
sqflite_native.databaseFactory (System-SQLite, kann kein SQLCipher) |
| Haupt-App Desktop | lib/core/matrix_client.dart:27 |
databaseFactoryFfi (gebündeltes sqlite3) |
| Push-Isolate (nur Android) | lib/core/background_push.dart _buildClient() |
sqflite_native.databaseFactory |
sqlcipher_flutter_libs ^0.6.8ist bereits inpubspec.yaml, wird nirgends benutzt.flutter_secure_storage ^10ist bereits da (→ Schlüsselablage im Android Keystore).- Beide Prozesse öffnen dieselbe Datei (
pyramid.sqlite, WAL-Modus,busy_timeout 5000).
Zielarchitektur
- Ein gemeinsamer DB-Öffner
lib/core/app_database.dart(neues Storage-Modul im Sinne von M2): kapselt Factory-Wahl, PRAGMAs, Schlüsselbeschaffung und Migration.matrix_client.dartundbackground_push.dartrufen NUR noch diese Fassade – damit verschwindet auch die heutige Code-Duplikation der PRAGMA-Blöcke. - Android: nicht mehr
sqflite_native, sonderndatabaseFactoryFfimit SQLCipher-Lib:Achtung Push-Isolate:import 'package:sqlite3/open.dart'; import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart'; open.overrideFor(OperatingSystem.android, openCipherOnAndroid);databaseFactoryFfispawnt ein eigenes Isolate – im Firebase-Background-Isolate ist das genau die Fallenklasse, an der schonNativeImplementationsIsolatescheiterte (Kommentar inbackground_push.dart). Dort deshalbdatabaseFactoryFfiNoIsolatebenutzen. - Schlüssel: 32 Byte kryptografisch zufällig (
Random.secure()), hex-kodiert influtter_secure_storageunterdb_cipher_key. Beide Prozesse lesen denselben Eintrag. Schlüssel wird bei ERSTER Nutzung erzeugt; existiert schon eine verschlüsselte DB, aber kein Schlüssel im Storage → harter Fehler mit Klartext-Meldung, NIEMALS still neue DB anlegen (Datenverlust-Regel!). - Öffnen:
PRAGMA key = "x'<hex>'"als allererstes Statement (OpenDatabaseOptions(onConfigure: ...)), danach wie bisher WAL + busy_timeout. - Windows/Linux-Desktop:
sqlcipher_flutter_libsliefert dort KEINE Binaries. Phase 1 = nur Android verschlüsseln (dort liegt das echte Risiko, dort sind die echten Nutzer); Desktop bleibt vorerst Klartext, klar im Code kommentiert. Desktop-SQLCipher später als eigener Punkt (eigene DLL bündeln).
Migration bestehender Klartext-DBs (der heikle Teil)
Reihenfolge beim App-Start, VOR MatrixSdkDatabase.init, VOR jedem Push-Zugriff:
- Erkennung: Datei beginnt mit
SQLite format 3\0→ Klartext → Migration nötig. (Verschlüsselte SQLCipher-Dateien haben keinen lesbaren Header.) - WAL eindampfen: Klartext-DB kurz normal öffnen,
PRAGMA wal_checkpoint(TRUNCATE), schließen – sonst verlieren wir Daten, die noch inpyramid.sqlite-walliegen. - Backup:
pyramid.sqlite→pyramid.sqlite.premigrationkopieren (Bytes, nicht rename). Bleibt bis zum verifizierten Erfolg liegen. - Export in NEUE Datei (Original bleibt unangetastet):
(läuft über die SQLCipher-FFI-Verbindung auf der Klartext-DB)
ATTACH DATABASE 'pyramid.enc.sqlite' AS enc KEY "x'<hex>'"; SELECT sqlcipher_export('enc'); DETACH DATABASE enc; - Verifikation:
pyramid.enc.sqlitemit Schlüssel öffnen,PRAGMA integrity_check== ok UND Zeilenzahl einer Kerntabelle (z. B.box_inbound_group_session, Name gegen matrix-6.2.0matrix_sdk_database.dart:139verifiziert) identisch mit Original. Schlägt IRGENDWAS fehl →pyramid.enc.sqlitelöschen, App startet normal mit Klartext-DB weiter, Fehler inauth_log.txt. Kein Zustand darf die Klartext-DB beschädigt zurücklassen. - Tausch: Original +
-wal/-shmlöschen,pyramid.enc.sqlite→pyramid.sqlite(rename, gleiche Partition = atomar). .premigration-Backup erst nach N erfolgreichen Tagen / manuell löschen (Entscheidung Bernd: sonst liegt weiter eine Klartext-Kopie herum – Kompromiss: nach 7 Tagen beim Start automatisch löschen).
Race mit dem Push-Isolate
Kommt ein FCM-Push, während die Haupt-App migriert, würde das Isolate die DB
mittendrin öffnen. Lösung: Marker-Datei pyramid.sqlite.migrating vor Schritt 2
anlegen, nach Schritt 6 löschen; _buildClient() im Push-Pfad bricht bei
vorhandenem Marker sofort ab (Notification zeigt dann einmalig den
„Neue Nachricht"-Platzhalter – akzeptabel). Zusätzlich erkennt das Isolate
selbst Klartext vs. verschlüsselt am Datei-Header und wählt PRAGMA key
entsprechend – es migriert aber NIE selbst.
Testplan (PC-Termin, in dieser Reihenfolge)
- Windows-Build mit Test-Account: Migration Klartext→SQLCipher durchlaufen lassen (Desktop kriegt fürs TESTEN die FFI-SQLCipher-Variante per lokalem Override, auch wenn Phase 1 sie im Release nicht aktiviert).
- Kernflow danach: Login erhalten? Alte verschlüsselte Nachrichten lesbar?
Logout → Neu-Login → immer noch lesbar? (
docs/PC_TESTPLAN.mdAbschnitt 1) - Fehlerpfade erzwingen: Migration mit vollem Datenträger / Kill mitten in Schritt 4 → App muss mit unversehrter Klartext-DB weiterlaufen.
- Android-Testgerät (eigenes, NICHT Uta): frische Installation (Neuanlage verschlüsselt), dann Update-Szenario (bestehende Klartext-DB migriert), dann Push-Test (Isolate liest verschlüsselte DB; secure_storage-Zugriff aus dem Background-Isolate explizit verifizieren – bekanntes Risiko).
- Erst wenn 1–4 grün: Release, Uta informieren (ihr Gerät migriert beim ersten Start automatisch; vorher ihr Key-Backup verifizieren!).
Bewusst NICHT im Scope
auth_log.txt,SharedPreferences, Media-Cache bleiben Klartext (keine Geheimnisse bzw. nur Cache; einzeln bewerten, falls Bernd es wünscht).- Passphrase-basierter Schlüssel (Nutzer-Eingabe): bewusst nein – Schlüssel liegt im Keystore, gleiche Vertrauensstufe wie der Rest der App-Daten, aber Datei-Exfiltration allein reicht dann nicht mehr.