# SecNote: Umstellung auf clientseitige Verschlüsselung

## Ziel
Umstellung von serverseitiger PHP-Verschlüsselung auf clientseitige JavaScript-Verschlüsselung mit AES-256-GCM. Der Server soll niemals Zugriff auf unverschlüsselte Nachrichten haben.

## Anforderungen

### Technische Spezifikation
- **Verschlüsselung:** AES-256-GCM im Browser (Web Crypto API)
- **Schlüsselgenerierung:** 256-bit zufälliger Schlüssel pro Nachricht
- **Schlüsselübertragung:** Via URL-Fragment (Hash), wird nicht an Server gesendet
- **Passwort-Modus:** Optional zusätzliche Passwort-Schicht (PBKDF2-Ableitung)
- **Bildverschlüsselung:** Bilder ebenfalls clientseitig verschlüsseln
- **Kompatibilität:** Moderne Browser (Chrome, Firefox, Safari, Edge)

### Funktionsweise

#### 1. Nachricht erstellen (Sender)
```
Nutzer gibt Nachricht ein
  ↓
Browser generiert zufälligen 256-bit Schlüssel
  ↓
Browser verschlüsselt Nachricht + Bild mit AES-256-GCM
  ↓
Verschlüsselte Daten werden an Server gesendet (Base64)
  ↓
Server speichert verschlüsselte Daten + gibt ID zurück
  ↓
Browser erstellt Share-Link: https://secnote.eblok.de/?id=abc123#BASE64_KEY
  ↓
Nutzer kopiert Link und teilt ihn
```

#### 2. Nachricht abrufen (Empfänger)
```
Nutzer öffnet Link: https://secnote.eblok.de/?id=abc123#BASE64_KEY
  ↓
Server erhält nur ?id=abc123 (Hash wird nicht übertragen!)
  ↓
Server liefert verschlüsselte Daten
  ↓
Browser liest Schlüssel aus URL-Hash (window.location.hash)
  ↓
Browser entschlüsselt Daten clientseitig
  ↓
Nutzer sieht Nachricht
```

### URL-Struktur
- **Basis:** `https://secnote.eblok.de/?id=NACHRICHT_ID#VERSCHLÜSSELUNGS_SCHLÜSSEL`
- **Beispiel:** `https://secnote.eblok.de/?id=7f3a9c#k=dGVzdGtleQ==`
- **Wichtig:** Alles nach `#` bleibt im Browser, Server sieht es nicht

## Implementierungsschritte

### Phase 1: JavaScript Crypto-Funktionen erstellen

Erstelle eine neue Datei `crypto.js` mit folgenden Funktionen:

1. **generateKey()** - Generiert zufälligen 256-bit AES-Schlüssel
2. **encryptData(plaintext, key)** - Verschlüsselt Text mit AES-256-GCM
3. **decryptData(ciphertext, key)** - Entschlüsselt Text
4. **encryptFile(file, key)** - Verschlüsselt Bild/Datei
5. **decryptFile(encryptedFile, key)** - Entschlüsselt Bild/Datei
6. **keyToBase64(key)** - Konvertiert Schlüssel zu Base64 für URL
7. **base64ToKey(base64)** - Konvertiert Base64 zurück zu Schlüssel

**Technische Details:**
- Nutze `crypto.subtle` (Web Crypto API)
- AES-256-GCM Modus
- IV (Initialization Vector) muss mit verschlüsselten Daten gespeichert werden
- Format: `IV (12 bytes) + verschlüsselte Daten + Auth-Tag`

### Phase 2: Frontend anpassen (index.html / create.html)

**Beim Erstellen einer Nachricht:**
1. Formular-Submit abfangen (preventDefault)
2. Schlüssel generieren
3. Nachricht verschlüsseln
4. Falls Bild vorhanden: Bild verschlüsseln
5. Verschlüsselte Daten (Base64) via AJAX an Server senden
6. Server-Antwort (ID) empfangen
7. Share-Link mit Schlüssel im Hash erstellen
8. Link dem Nutzer anzeigen zum Kopieren

**Beispiel-Flow:**
```javascript
document.getElementById('createForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const message = document.getElementById('message').value;
    const password = document.getElementById('password').value; // optional
    const imageFile = document.getElementById('image').files[0]; // optional
    
    // 1. Schlüssel generieren
    const key = await generateKey();
    
    // 2. Nachricht verschlüsseln
    const encryptedMessage = await encryptData(message, key);
    
    // 3. Bild verschlüsseln (falls vorhanden)
    let encryptedImage = null;
    if (imageFile) {
        encryptedImage = await encryptFile(imageFile, key);
    }
    
    // 4. An Server senden
    const response = await fetch('/api/create.php', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            encrypted_message: encryptedMessage,
            encrypted_image: encryptedImage,
            has_password: !!password,
            password_hash: password ? await hashPassword(password) : null
        })
    });
    
    const data = await response.json();
    const messageId = data.id;
    
    // 5. Share-Link erstellen
    const keyBase64 = await keyToBase64(key);
    const shareLink = `${window.location.origin}/?id=${messageId}#${keyBase64}`;
    
    // 6. Link anzeigen
    document.getElementById('shareLink').value = shareLink;
});
```

### Phase 3: Frontend anpassen (view.html / Anzeige)

**Beim Abrufen einer Nachricht:**
1. ID aus URL-Parameter lesen (`?id=xyz`)
2. Schlüssel aus URL-Hash lesen (`#key`)
3. Verschlüsselte Daten vom Server abrufen
4. Falls Passwort erforderlich: Passwort-Dialog anzeigen
5. Daten entschlüsseln
6. Nachricht + Bild anzeigen

**Beispiel-Flow:**
```javascript
async function loadMessage() {
    // 1. ID und Key aus URL lesen
    const urlParams = new URLSearchParams(window.location.search);
    const messageId = urlParams.get('id');
    const keyBase64 = window.location.hash.substring(1); // ohne '#'
    
    if (!keyBase64) {
        alert('Fehler: Kein Entschlüsselungsschlüssel in URL!');
        return;
    }
    
    // 2. Verschlüsselte Daten vom Server holen
    const response = await fetch(`/api/get.php?id=${messageId}`);
    const data = await response.json();
    
    // 3. Passwort prüfen (falls erforderlich)
    if (data.requires_password) {
        const password = prompt('Passwort eingeben:');
        if (!verifyPassword(password, data.password_hash)) {
            alert('Falsches Passwort!');
            return;
        }
    }
    
    // 4. Schlüssel rekonstruieren
    const key = await base64ToKey(keyBase64);
    
    // 5. Nachricht entschlüsseln
    const decryptedMessage = await decryptData(data.encrypted_message, key);
    
    // 6. Bild entschlüsseln (falls vorhanden)
    let decryptedImage = null;
    if (data.encrypted_image) {
        decryptedImage = await decryptFile(data.encrypted_image, key);
    }
    
    // 7. Anzeigen
    document.getElementById('messageText').textContent = decryptedMessage;
    if (decryptedImage) {
        document.getElementById('messageImage').src = URL.createObjectURL(decryptedImage);
    }
    
    // 8. Nachricht auf Server löschen
    await fetch(`/api/delete.php?id=${messageId}`, {method: 'POST'});
}

// Beim Laden der Seite
window.addEventListener('DOMContentLoaded', loadMessage);
```

### Phase 4: Backend anpassen (PHP)

**Änderungen in create.php:**
- Entferne alle Verschlüsselungs-Logik
- Empfange verschlüsselte Daten direkt vom Client
- Speichere verschlüsselte Daten 1:1 in Datenbank
- Gebe nur die Message-ID zurück

**Änderungen in get.php:**
- Entferne alle Entschlüsselungs-Logik
- Liefere verschlüsselte Daten 1:1 zurück
- Prüfe nur Passwort-Hash (falls vorhanden)

**Beispiel create.php:**
```php
<?php
$data = json_decode(file_get_contents('php://input'), true);

// KEINE Verschlüsselung mehr hier!
$encrypted_message = $data['encrypted_message']; // bereits verschlüsselt vom Client
$encrypted_image = $data['encrypted_image'] ?? null;
$password_hash = $data['password_hash'] ?? null;

// In Datenbank speichern
$id = generateRandomId();
$stmt = $pdo->prepare("INSERT INTO messages (id, encrypted_content, encrypted_image, password_hash, created_at) VALUES (?, ?, ?, ?, NOW())");
$stmt->execute([$id, $encrypted_message, $encrypted_image, $password_hash]);

// Nur ID zurückgeben
echo json_encode(['success' => true, 'id' => $id]);
?>
```

**Beispiel get.php:**
```php
<?php
$id = $_GET['id'] ?? null;

$stmt = $pdo->prepare("SELECT encrypted_content, encrypted_image, password_hash FROM messages WHERE id = ?");
$stmt->execute([$id]);
$message = $stmt->fetch();

if (!$message) {
    http_response_code(404);
    echo json_encode(['error' => 'Nachricht nicht gefunden oder bereits gelöscht']);
    exit;
}

// Verschlüsselte Daten zurückgeben (KEINE Entschlüsselung!)
echo json_encode([
    'success' => true,
    'encrypted_message' => $message['encrypted_content'],
    'encrypted_image' => $message['encrypted_image'],
    'requires_password' => !empty($message['password_hash']),
    'password_hash' => $message['password_hash'] // für Client-Verifizierung
]);

// Nachricht löschen (one-time read)
$stmt = $pdo->prepare("DELETE FROM messages WHERE id = ?");
$stmt->execute([$id]);
?>
```

### Phase 5: Datenbankschema anpassen

**Tabelle `messages` aktualisieren:**
```sql
ALTER TABLE messages 
    MODIFY COLUMN encrypted_content MEDIUMTEXT NOT NULL COMMENT 'Client-verschlüsselter Inhalt (Base64)',
    MODIFY COLUMN encrypted_image MEDIUMBLOB NULL COMMENT 'Client-verschlüsseltes Bild (Base64)',
    ADD COLUMN requires_password BOOLEAN DEFAULT FALSE COMMENT 'Ob Passwort erforderlich ist';
```

### Phase 6: UI/UX Verbesserungen

1. **Loading-Indikatoren:** Verschlüsselung kann 100-500ms dauern
2. **Fehlerbehandlung:** 
   - Kein Schlüssel in URL → Fehlermeldung
   - Entschlüsselung fehlgeschlagen → "Nachricht beschädigt"
   - Browser zu alt → "Browser nicht unterstützt"
3. **Browser-Kompatibilität prüfen:**
   ```javascript
   if (!window.crypto || !window.crypto.subtle) {
       alert('Ihr Browser unterstützt keine sichere Verschlüsselung. Bitte nutzen Sie einen modernen Browser.');
   }
   ```

4. **Sicherheitshinweis anzeigen:**
   - "Diese Nachricht wird in Ihrem Browser verschlüsselt"
   - "Der Server hat niemals Zugriff auf Ihre unverschlüsselte Nachricht"

## Passwort-Feature (Optional, zusätzliche Sicherheit)

Falls der Nutzer ein Passwort setzt:
1. Aus Passwort wird mit PBKDF2 ein zweiter Schlüssel abgeleitet
2. Die bereits verschlüsselte Nachricht wird nochmal mit diesem Schlüssel verschlüsselt (Double-Encryption)
3. Beim Abrufen: Erst Passwort-Entschlüsselung, dann Hauptschlüssel-Entschlüsselung

**Vorteil:** Selbst wenn jemand den Link hat, braucht er trotzdem das Passwort.

## Testing-Checkliste

- [ ] Nachricht erstellen und abrufen funktioniert
- [ ] Bild-Upload und -Anzeige funktioniert
- [ ] Passwort-Schutz funktioniert
- [ ] Link ohne Hash zeigt Fehlermeldung
- [ ] Falsches Passwort wird abgelehnt
- [ ] Nachricht wird nach Abruf gelöscht
- [ ] Nachricht wird nach 3 Tagen gelöscht (Cronjob)
- [ ] Funktioniert in Chrome, Firefox, Safari, Edge
- [ ] Mobile Browser funktionieren
- [ ] Sehr lange Nachrichten (>10KB) funktionieren
- [ ] Große Bilder (>2MB) funktionieren
- [ ] Mehrere Nachrichten parallel funktionieren

## Sicherheitsaspekte

**Was der Server NICHT mehr sieht:**
- ✅ Inhalt der Nachricht
- ✅ Inhalt des Bildes
- ✅ Verschlüsselungsschlüssel

**Was der Server noch sieht:**
- ⚠️ Wann eine Nachricht erstellt wurde
- ⚠️ Wann eine Nachricht abgerufen wurde
- ⚠️ Ungefähre Größe der Nachricht
- ⚠️ Ob ein Passwort gesetzt wurde

**Zusätzliche Empfehlungen:**
- HTTPS ist PFLICHT (sonst Man-in-the-Middle möglich)
- Content Security Policy (CSP) Header setzen
- Keine Browser-Konsole-Logs mit sensiblen Daten

## Datenschutzerklärung aktualisieren

Nach erfolgreicher Umstellung:
1. Gelben Warnhinweis entfernen
2. Text ändern zu: "Die Verschlüsselung erfolgt clientseitig in Ihrem Browser"
3. Ergänzen: "Der Server hat niemals Zugriff auf Ihre unverschlüsselten Nachrichten"

## Rollback-Plan

Falls Probleme auftreten:
1. Alte PHP-Version als Backup behalten
2. Feature-Flag einbauen um zwischen alt/neu zu wechseln
3. Schrittweise Migration: Neue Nachrichten mit Client-Crypto, alte noch mit PHP

## Code-Struktur Übersicht

```
secnote/
├── index.html              # Hauptseite (angepasst)
├── create.html            # Nachricht erstellen (angepasst)
├── view.html              # Nachricht anzeigen (angepasst)
├── js/
│   ├── crypto.js          # NEU: Crypto-Funktionen
│   ├── create.js          # NEU: Create-Logik
│   └── view.js            # NEU: View-Logik
├── api/
│   ├── create.php         # Angepasst (keine Verschlüsselung)
│   ├── get.php            # Angepasst (keine Entschlüsselung)
│   └── delete.php         # Unverändert
└── config.php             # Unverändert
```

## Priorität der Umsetzung

1. **Kritisch:** crypto.js mit allen Verschlüsselungsfunktionen
2. **Kritisch:** Backend-Anpassungen (keine Ver-/Entschlüsselung mehr)
3. **Wichtig:** Frontend Create-Flow
4. **Wichtig:** Frontend View-Flow
5. **Nice-to-have:** Passwort-Double-Encryption
6. **Nice-to-have:** UI-Verbesserungen und Ladeanimationen

## Fragen vor Start

- Soll das Passwort-Feature beibehalten werden? (Empfehlung: Ja, mit Double-Encryption)
- Maximale Dateigröße für Bilder? (Aktuell unbekannt)
- Soll alte Datenbank migriert werden oder neue Messages nur mit neuer Methode?
- Welche Browser-Versionen mindestens unterstützen? (Empfehlung: Letzte 2 Jahre)

## Nächste Schritte

1. Code in GitHub/GitLab commiten vor Änderungen
2. crypto.js implementieren und isoliert testen
3. Backend anpassen
4. Frontend Schritt für Schritt umstellen
5. Testen, testen, testen
6. Datenschutzerklärung aktualisieren
7. Deploy

Viel Erfolg! 🔐
