# 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 test` deckt 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 als `sqlite3.dll`/`libsqlite3.so` mit (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.8` ist bereits in `pubspec.yaml`, wird nirgends benutzt. - `flutter_secure_storage ^10` ist bereits da (→ Schlüsselablage im Android Keystore). - Beide Prozesse öffnen **dieselbe Datei** (`pyramid.sqlite`, WAL-Modus, `busy_timeout 5000`). ## Zielarchitektur 1. **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.dart` und `background_push.dart` rufen NUR noch diese Fassade – damit verschwindet auch die heutige Code-Duplikation der PRAGMA-Blöcke. 2. **Android:** nicht mehr `sqflite_native`, sondern `databaseFactoryFfi` mit SQLCipher-Lib: ```dart import 'package:sqlite3/open.dart'; import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart'; open.overrideFor(OperatingSystem.android, openCipherOnAndroid); ``` **Achtung Push-Isolate:** `databaseFactoryFfi` spawnt ein eigenes Isolate – im Firebase-Background-Isolate ist das genau die Fallenklasse, an der schon `NativeImplementationsIsolate` scheiterte (Kommentar in `background_push.dart`). Dort deshalb `databaseFactoryFfiNoIsolate` benutzen. 3. **Schlüssel:** 32 Byte kryptografisch zufällig (`Random.secure()`), hex-kodiert in `flutter_secure_storage` unter `db_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!). 4. **Öffnen:** `PRAGMA key = "x''"` als allererstes Statement (`OpenDatabaseOptions(onConfigure: ...)`), danach wie bisher WAL + busy_timeout. 5. **Windows/Linux-Desktop:** `sqlcipher_flutter_libs` liefert 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: 1. Erkennung: Datei beginnt mit `SQLite format 3\0` → Klartext → Migration nötig. (Verschlüsselte SQLCipher-Dateien haben keinen lesbaren Header.) 2. WAL eindampfen: Klartext-DB kurz normal öffnen, `PRAGMA wal_checkpoint(TRUNCATE)`, schließen – sonst verlieren wir Daten, die noch in `pyramid.sqlite-wal` liegen. 3. **Backup:** `pyramid.sqlite` → `pyramid.sqlite.premigration` kopieren (Bytes, nicht rename). Bleibt bis zum verifizierten Erfolg liegen. 4. Export in NEUE Datei (Original bleibt unangetastet): ```sql ATTACH DATABASE 'pyramid.enc.sqlite' AS enc KEY "x''"; SELECT sqlcipher_export('enc'); DETACH DATABASE enc; ``` (läuft über die SQLCipher-FFI-Verbindung auf der Klartext-DB) 5. Verifikation: `pyramid.enc.sqlite` mit Schlüssel öffnen, `PRAGMA integrity_check` == ok UND Zeilenzahl einer Kerntabelle (z. B. `box_inbound_group_session`, Name gegen matrix-6.2.0 `matrix_sdk_database.dart:139` verifiziert) identisch mit Original. Schlägt IRGENDWAS fehl → `pyramid.enc.sqlite` löschen, App startet normal mit Klartext-DB weiter, Fehler in `auth_log.txt`. **Kein Zustand darf die Klartext-DB beschädigt zurücklassen.** 6. Tausch: Original + `-wal`/`-shm` löschen, `pyramid.enc.sqlite` → `pyramid.sqlite` (rename, gleiche Partition = atomar). 7. `.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) 1. 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). 2. Kernflow danach: Login erhalten? Alte verschlüsselte Nachrichten lesbar? Logout → Neu-Login → immer noch lesbar? (`docs/PC_TESTPLAN.md` Abschnitt 1) 3. Fehlerpfade erzwingen: Migration mit vollem Datenträger / Kill mitten in Schritt 4 → App muss mit unversehrter Klartext-DB weiterlaufen. 4. 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). 5. 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.