# 06 — E2EE Verschlüsselung --- ## Inhaltsverzeichnis - [Setup & Initialisierung](#setup--initialisierung) - [Verschlüsselung aktivieren](#verschlüsselung-aktivieren) - [Entschlüsselung & Fehlerfälle](#entschlüsselung--fehlerfälle) - [Device-Verifikation (SAS)](#device-verifikation-sas) - [Cross-Signing](#cross-signing) - [Key Backup](#key-backup) - [Fehlende Session Keys anfordern](#fehlende-session-keys-anfordern) - [Geräte-Keys abfragen](#geräte-keys-abfragen) - [Trust-Status](#trust-status) --- ## Setup & Initialisierung ```dart // flutter_vodozemac muss initialisiert sein BEVOR Client erstellt wird import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod; await vod.init(); // Client mit E2EE-Support final client = Client( 'Pyramid', database: await MatrixSdkDatabase.init('pyramid', database: db), nativeImplementations: NativeImplementationsIsolate(compute), // Oder für Hintergrund-Isolaten (kein compute): // nativeImplementations: NativeImplementationsDummy(), verificationMethods: { KeyVerificationMethod.numbers, KeyVerificationMethod.emoji, // KeyVerificationMethod.qrCodeScan, // falls QR-Scanner vorhanden // KeyVerificationMethod.qrCodeShow, }, ); await client.init( waitForFirstSync: false, waitUntilLoadCompletedLoaded: false, ); // E2EE verfügbar prüfen print(client.encryptionEnabled); // true wenn Vodozemac/Olm geladen ``` --- ## Verschlüsselung aktivieren ```dart // Raum-Verschlüsselung einschalten (PERMANENT, nicht rückgängig!) if (!room.isEncrypted) { await room.enableEncryption(); } // Verschlüsselung beim Erstellen aktivieren final roomId = await client.createRoom( name: 'Verschlüsselter Raum', enableEncryption: true, preset: CreateRoomPreset.privateChat, ); // Status prüfen print(room.isEncrypted); // bool print(room.encryptionAlgorithm); // 'm.megolm.v1.aes-sha2' ``` --- ## Entschlüsselung & Fehlerfälle ```dart // Beim Anzeigen von Events prüfen: if (event.isBadEncrypted) { // Anzeigen: "Nachricht kann nicht entschlüsselt werden" // Mögliche Ursachen: // - Gerät war nicht in der Raumschlüssel-Verteilung // - Session fehlt (Megolm Session nicht vorhanden) // - Falsches Gerät / fehlende Olm-Session } // Entschlüsselungs-Fehler-Typ if (event.content['can_not_decrypt'] != null) { // Generischer Fehler } if (event.type == EventTypes.Encrypted && event.plaintextContent == null) { // Noch nicht entschlüsselt } // Event-Body bei Fehler prüfen final body = event.body; // Enthält "** Unable to decrypt: ..." bei Fehlern // Fehlende Schlüssel anfordern (von anderen Geräten) await client.requestMissingSessionKeys(); ``` --- ## Device-Verifikation (SAS) ```dart // ── Verifikation starten (ich verifiziere ein anderes Gerät) ────────────── // Verifikations-Anfrage senden final request = await client.userDeviceKeys[otherUserId]! .startVerification(); // request.onUpdate: Stream mit Zustandsänderungen // ── Verifikation empfangen (anderes Gerät will mich verifizieren) ───────── // Eingehende Anfragen beobachten client.onKeyVerificationRequest.stream.listen((request) async { // Anfrage annehmen await request.acceptVerification(); }); // ── SAS-Flow ───────────────────────────────────────────────────────────── // Auf SAS-Ready warten und Emojis/Zahlen anzeigen request.onUpdate.stream.listen((_) async { if (request.state == KeyVerificationState.showSas) { // Emojis anzeigen final emojis = request.sasEmojis; // List for (final emoji in emojis) { print('${emoji.emoji} ${emoji.name}'); } // Oder Dezimalzahlen final decimals = request.sasDecimals; // List (3 Zahlen) print(decimals); // z.B. [1234, 5678, 9012] // Wenn User bestätigt dass sie übereinstimmen: await request.confirmSas(); // Wenn sie nicht übereinstimmen: // await request.rejectSas(); } if (request.state == KeyVerificationState.done) { print('Verifikation erfolgreich!'); } if (request.state == KeyVerificationState.error) { print('Verifikation fehlgeschlagen: ${request.cancelCode}'); } }); // ── QR-Code Verifikation ───────────────────────────────────────────────── // QR-Code zum Anzeigen generieren final qrData = await request.generateQrCodeData(); // qrData als QR-Code rendern (z.B. mit qr_flutter-Package) // Gescannten QR-Code verarbeiten await request.scanQrCode(scannedData); // Bestätigen dass QR gescannt wurde await request.confirmQrCodeScanned(); ``` --- ## Cross-Signing ```dart // Cross-Signing bootstrappen (erstellt Master-, Self- und User-Signing-Keys) // Nur einmal pro Account nötig await client.encryption!.bootstrapCrossSigning( setupMasterKey: true, setupSelfSigningKey: true, setupUserSigningKey: true, ); // Cross-Signing-Status final crossSigning = client.encryption!.crossSigning; print(crossSigning.enabled); // bool print(crossSigning.selfSigned); // bool: eigenes Gerät ist self-signed // Eigenes Gerät ist verifiziert? final myDevice = client.userDeviceKeys[client.userID] ?.deviceKeys[client.deviceID]; print(myDevice?.verified); // bool // Anderen User als vertrauenswürdig markieren await client.encryption!.crossSigning.signUser(otherUserId); // Gerät verifizieren (nach SAS/QR) await myDevice?.setVerified(true); ``` --- ## Key Backup ```dart // ── Key Backup einrichten ───────────────────────────────────────────────── // Vollständiges Crypto-Setup (empfohlen): // Erstellt Secret Storage + Cross-Signing + Key Backup await client.bootstrapCryptoIdentity( wipeRecovery: false, // true: bestehende Recovery überschreiben ); // Nur Key Backup: final backup = client.encryption!.keyManager; // Recovery-Key generieren und anzeigen final recoveryKey = await client.encryption!.ssss.generateKey(); print(recoveryKey); // Langer alphanumerischer Schlüssel → User aufschreiben lassen! // ── Key Backup Status ───────────────────────────────────────────────────── // Ist Key Backup aktiv? final keyBackupEnabled = client.encryption!.keyManager.enabled; // Alle Sessions hochladen await client.encryption!.keyManager.uploadInboundGroupSessions(); // ── Backup wiederherstellen (Recovery) ─────────────────────────────────── // Keys von Server-Backup wiederherstellen await client.encryption!.keyManager.loadFromResponse( await client.getRoomKeysBackup(), ); // Oder mit Recovery-Key: await client.encryption!.ssss.unlock(recoveryKey: eingabe); await client.encryption!.keyManager.restore(); ``` --- ## Fehlende Session Keys anfordern ```dart // Schlüssel von anderen Geräten desselben Users anfordern await client.requestMissingSessionKeys(); // Für einen spezifischen Raum await client.requestMissingSessionKeys( rooms: [room], ); // Einzelne Session anfordern (für ein spezifisches Event) if (event.isBadEncrypted) { final sessionId = event.content .tryGet('session_id'); final senderKey = event.content .tryGet('sender_key'); if (sessionId != null) { await room.requestKey(sessionId); } } ``` --- ## Geräte-Keys abfragen ```dart // Alle Geräte eines Users laden final userDevices = await client.getUserDeviceKeys(userId); // → Map // Geräte eines Raums (alle Mitglieder) final roomDevices = await room.getUserDeviceKeys(); // Eigene Geräte final myDevices = client.userDeviceKeys[client.userID]?.deviceKeys; // Gerät-Details for (final device in myDevices?.values ?? []) { print(device.deviceId); print(device.displayName); print(device.verified); print(device.blocked); print(device.ed25519Key); // Signatur-Schlüssel print(device.curve25519Key); // Encryption-Schlüssel } // Gerät sperren (Nachrichten werden nicht mehr entschlüsselt gesendet) await myDevices?['DEVICEID']?.setBlocked(true); ``` --- ## Trust-Status ```dart // Trust-Level für Raum berechnen final encryption = room.client.encryption; if (encryption != null) { final trust = await room.calcEncryptionHealthState(); // trust: .allVerified | .unverifiedDevices | .unknown } // User-Trust final userTrust = client.userDeviceKeys[userId]?.verified; // null = unbekannt, true = verifiziert, false = blockiert // Nachrichten-Verifikation if (!event.encryptionHealthState.allVerified) { // Anzeigen: "Gesendet von unverifiziertem Gerät" } // Eigene Identität final myIdentity = client.encryption?.crossSigning; print(myIdentity?.selfSigned); // Eigenes Gerät cross-signed? ```