Zum Inhalt springen

Sicherheit & Technologie

Verschlüsselungs­architektur

Version 1.0 · Stand: April 2026

Dieses Dokument beschreibt die kryptografischen Verfahren, die Zotto zum Schutz deiner Daten einsetzt. Es richtet sich an technisch versierte Nutzer sowie Sicherheitsforscher.

1. Zero-Knowledge-Prinzip

Zotto verwendet eine Zero-Knowledge-Architektur: Alle Daten werden ausschließlich im Browser des Nutzers ver- und entschlüsselt. Der Server empfängt, speichert und überträgt ausschließlich Ciphertext — zu keinem Zeitpunkt existiert Klartext auf Zotto-Servern.

Das bedeutet konkret: Selbst ein vollständiger Datenbankdump wäre für einen Angreifer ohne die geheimen Schlüssel des Nutzers wertlos. Zotto als Betreiber kann die Daten der Nutzer nicht lesen — auch nicht auf richterliche Anordnung.

Nutzer-Browser          Zotto-Server
     │                       │
     │  POST /api/entries     │
     │  { iv, ciphertext }   ──────────▶  Speichert Ciphertext
     │                       │
     │  GET /api/entries      │
     │  { iv, ciphertext }   ◀──────────  Liefert Ciphertext
     │                       │
     │  AES-GCM Decrypt       │
     │  → Klartext im RAM     │
     │  (verlässt nie Client) │

2. Schlüsselableitung — Argon2id

Das Passwort des Nutzers wird niemals direkt als Schlüssel verwendet. Stattdessen wird daraus mittels Argon2id ein kryptografisch starker Wrapping-Key abgeleitet. Argon2id ist der Gewinner des Password Hashing Competition (PHC) 2015 und gilt als Stand der Technik für Passwort-basierte Schlüsselableitung (RFC 9106).

Parameter

Parameter Wert Begründung
type Argon2id Resistenz gegen Side-Channel und GPU-Angriffe
time 3 Iterationen OWASP-Empfehlung für interaktive Anwendungen
mem 65 536 KiB (64 MB) Erschwerung von Brute-Force via GPU/ASIC
parallelism 4 Ausnutzung moderner Multi-Core-CPUs
hashLen 32 Bytes (256 Bit) Passend für AES-256-Wrapping-Key
salt 16 Bytes, zufällig Pro Nutzer einmalig, serverseitig gespeichert

Der Salt wird bei der Ersteinrichtung einmalig mit crypto.getRandomValues() erzeugt und zusammen mit den KDF-Parametern auf dem Server gespeichert. Er dient ausschließlich dazu, Rainbow-Table-Angriffe unmöglich zu machen — er ist kein Geheimnis.

Auf einem modernen Consumer-Laptop (Apple M3, 2024) dauert die Ableitung mit diesen Parametern ca. 300–800 ms. Das macht Brute-Force-Angriffe auf ein durchschnittliches Passwort mit 12+ Zeichen praktisch undurchführbar.

3. Master Encryption Key (MEK)

Der Master Encryption Key (MEK) ist ein 256-Bit-Zufallsschlüssel, der bei der E2E-Einrichtung einmalig im Browser mit crypto.getRandomValues(new Uint8Array(32)) generiert wird.

Der MEK wird nicht aus dem Passwort abgeleitet — das Passwort erzeugt nur den Wrapping-Key (via Argon2id), der den MEK einschließt. Dieser Designentscheid ermöglicht Passwortänderungen ohne Neu-Verschlüsselung aller Einträge: Es wird nur der eMEK-Blob neu erstellt.

Registrierung:
  1. MEK  ← crypto.getRandomValues(32 bytes)      [256 Bit, bleibt im Browser]
  2. WK   ← Argon2id(password, salt, params)       [Wrapping Key, 256 Bit]
  3. eMEK ← AES-256-GCM(MEK, WK)                  [Encrypted MEK]
  4. POST /api/e2e/setup  { eMEK, salt, params }   [MEK verlässt Browser NIE]

Login:
  1. GET /api/e2e/mek  → { eMEK, salt, params }
  2. WK   ← Argon2id(password, salt, params)
  3. MEK  ← AES-256-GCM-Decrypt(eMEK, WK)         [MEK nur im RAM]

Der MEK lebt ausschließlich im Browser-RAM (Alpine.js-Store). Er wird bei jedem Page-Reload verworfen — ein bewusstes Sicherheitsdesign, das verhindert, dass ein Angreifer mit Zugriff auf den Browser-Speicher (z.B. via XSS) den Schlüssel persistent extrahieren kann.

4. Key-Wrapping & Key-Slots

Zotto unterstützt mehrere Key-Slots pro Nutzer. Jeder Slot enthält eine separate, mit einem anderen Schlüssel gewrappte Kopie des MEK (oder eines zweiten MEK für den Vorschau-Modus).

Slot Feld Zweck
0 (Primary) encrypted_mek_primary MEK verschlüsselt mit Passwort-Wrapping-Key
Recovery encrypted_mek_recovery MEK verschlüsselt mit Recovery-Key-Wrapping-Key (eigener Salt)
1 (Preview) encrypted_mek_preview Separater MEK für den Vorschau-Modus (FakePin)

Vorschau-Modus (FakePin): Slot 1 enthält einen völlig eigenständigen MEK, der harmlose Dummy-Einträge verschlüsselt. Server-seitig sind beide Slots identisch strukturiert — der Server kann nicht unterscheiden, welcher Slot beim Entsperren verwendet wird. Dies ermöglicht eine glaubhafte Abstreitbarkeit (plausible deniability) unter Zwang.

Das Key-Wrapping selbst erfolgt via AES-256-GCM mit einem zufälligen 96-Bit-IV: { ct: base64(ciphertext+tag), iv: base64(12 bytes) }. Der 128-Bit-Authentication-Tag ist dabei im Ciphertext-Buffer enthalten (Web Crypto API-Konvention).

5. Eintrags-Verschlüsselung — AES-256-GCM

Jeder Eintrag wird mit AES-256-GCM (Advanced Encryption Standard, 256-Bit-Schlüssel, Galois/Counter Mode) verschlüsselt. AES-256-GCM ist ein authentifiziertes Verschlüsselungsverfahren (AEAD) und bietet sowohl Vertraulichkeit als auch Integrität und Authentizität der Daten.

Für jeden Eintrag wird ein einzigartiger, zufälliger 96-Bit-IV (Initialization Vector) generiert. Die Wiederverwendung eines IV mit demselben Schlüssel würde die Sicherheitsgarantien von GCM brechen — Zotto stellt durch die Verwendung von crypto.getRandomValues() sicher, dass dies nie passiert.

Klartext-Payload (JSON):
  { "a": 35.00, "d": "Haarschnitt", "p": "cash" }

Verschlüsselung:
  iv         ← crypto.getRandomValues(12 bytes)
  ciphertext ← AES-256-GCM-Encrypt(JSON, MEK, iv)
               [ciphertext enthält 128-Bit Auth-Tag]

Gespeichert auf Server:
  { encrypted_payload: base64(ciphertext), iv: base64(iv) }

Der 128-Bit-Authentication-Tag (GCM-MAC) schützt gegen Manipulation: Jede Veränderung des Ciphertexts oder des IV durch einen Angreifer führt bei der Entschlüsselung zu einem Fehler. Daten können also nicht unbemerkt verändert werden.

6. Recovery Key

Der Recovery Key ist ein 256-Bit-Zufallsschlüssel, der bei der E2E-Einrichtung einmalig generiert und dem Nutzer als lesbarer Hex-String (Format: XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX) angezeigt wird.

Er funktioniert als dritter Wrapping-Key für den MEK (mit eigenem Argon2id-Salt) und ermöglicht die Wiederherstellung des MEK bei vergessenem Passwort. Der Server speichert ausschließlich den SHA-256-Hash des Recovery Keys — für eine zukünftige Prüfung ob der eingegebene Key korrekt ist, ohne den Key selbst zu kennen.

⚠ Wichtig: Wird der Recovery Key verloren und das Passwort vergessen, sind die Daten unwiederbringlich verloren. Zotto hat keine Möglichkeit, Daten ohne die Schlüssel des Nutzers zu entschlüsseln.

7. Transport-Sicherheit

Die gesamte Kommunikation zwischen Browser und Server erfolgt ausschließlich über HTTPS mit TLS 1.3. TLS 1.2 und ältere Versionen sind deaktiviert. Die Produktionsinfrastruktur verwendet Cloudflare als CDN mit aktivierter WAF und DDoS-Schutz.

  • TLS 1.3 mit Perfect Forward Secrecy (ECDHE)
  • HSTS (HTTP Strict Transport Security) mit langer max-age
  • Cipher-Suites ausschließlich mit AEAD (AES-GCM, ChaCha20-Poly1305)
  • CSRF-Schutz via SameSite-Cookies und Laravel CSRF-Token
  • Content-Security-Policy (CSP) verhindert Code-Injection

Da alle Daten bereits vor der Übertragung verschlüsselt sind, bietet die Transportverschlüsselung eine zweite Schutzschicht — sie ist aber nicht die primäre Verteidigungslinie.

8. Was der Server speichert

Vollständige Übersicht der sicherheitsrelevanten Felder in der Datenbank:

Feld Inhalt Geheimnis?
encrypted_mek_primary AES-256-GCM verschlüsselter MEK Ja — ohne Passwort wertlos
encrypted_mek_recovery MEK verschlüsselt mit Recovery-Key Ja — ohne Recovery Key wertlos
key_derivation_salt Argon2id-Salt (16 Bytes) Nein — kein Geheimnis
key_derivation_params Argon2id-Parameter (time, mem, parallelism) Nein — kein Geheimnis
recovery_key_hash SHA-256(Recovery Key) Kein Vorteil für Angreifer
encrypted_payload AES-256-GCM Ciphertext des Eintrags Ja — ohne MEK wertlos
iv GCM-IV (12 Bytes) des Eintrags Nein — kein Geheimnis
password (hash) bcrypt/Argon2id-Hash des Passworts Ja — für Auth, nicht für Entschlüsselung

Der Passwort-Hash auf dem Server dient ausschließlich der Authentifizierung — er ist nicht zur Entschlüsselung geeignet, da der Encryption-Key via Argon2id mit anderen Parametern abgeleitet wird als der Auth-Hash.

9. Web Crypto API & WASM

Alle kryptografischen Operationen (außer Argon2id) werden mit der Web Crypto API (W3C-Standard, window.crypto.subtle) durchgeführt. Diese API ist in allen modernen Browsern nativ implementiert (Chrome 37+, Firefox 34+, Safari 11+, Edge 12+) und läuft in einer priviligierten, von JavaScript isolierten Umgebung.

Argon2id ist nicht Teil der Web Crypto API und wird via WebAssembly (WASM) bereitgestellt (argon2-browser). Der WASM-Code ist deterministische Kompilierung von C-Referenz-Code und erzeugt identische Ausgaben wie native Argon2id-Implementierungen.

  • AES-256-GCM Verschlüsselung/Entschlüsselung: crypto.subtle.encrypt/decrypt
  • Zufallszahlengenerator: crypto.getRandomValues() — CSPRNG des Betriebssystems
  • Key Import: crypto.subtle.importKey('raw', ...)
  • SHA-256: crypto.subtle.digest('SHA-256', ...)
  • Argon2id: WASM via argon2-browser

10. Bedrohungsmodell

✓ Schutz gegen

  • Datenbankdump — alle Einträge sind Ciphertext, ohne MEK wertlos
  • Betreiber-Zugriff — Zero-Knowledge, kein Klartext auf Servern
  • Jeder Dritte ohne Ausnahme — Zero-Knowledge ist absolut: kein Betreiber, keine Institution, niemand kann den Inhalt lesen — selbst auf richterliche Anordnung ist nur unlesbarer Ciphertext herausgebbar
  • Brute-Force auf Passwort — Argon2id mit 64 MB RAM-Bedarf macht Angriffe unpraktisch
  • Manipulation von Einträgen — GCM-Auth-Tag erkennt Veränderungen
  • Netzwerk-Abhören — TLS 1.3 + bereits verschlüsselte Daten
  • Ungewollte Einsicht — Vorschau-Modus (FakePin) mit glaubhafter Abstreitbarkeit für sensible Situationen

⚠ Nicht im Schutzbereich

  • Kompromittierter Browser — Malware im Browser kann den MEK im RAM lesen
  • Schwaches Passwort — Argon2id verlangsamt Angriffe, ersetzt aber kein starkes Passwort
  • Verlorener Recovery Key + vergessenes Passwort — unwiderruflicher Datenverlust
  • Physischer Gerätezugriff bei entsperrter App — MEK ist im RAM, Auto-Lock schützt bei Inaktivität
  • Metadaten — Zeitstempel, Eintragsanzahl und Zugriffszeiten sind serverseitig sichtbar

11. Bekannte Grenzen & Roadmap

Zotto befindet sich in aktiver Entwicklung. Folgende Sicherheits-Features sind geplant, aber noch nicht implementiert:

  • Passkey-basiertes MEK-Unlock via WebAuthn PRF — Verwendung der prf-Extension der WebAuthn API, um den MEK ohne Passwort zu entschlüsseln (Chrome 116+, RFC 8809)
  • Verschlüsselter Backup-Export — Client-seitiger Export aller Einträge als verschlüsseltes Archiv (AES-256-GCM)
  • Multi-User-E2E (Team Data Key) — Shared-Key-Architektur für mehrere Kassierer (TDK, per-User Key-Wrapping)
  • Key-Rotation — Automatische Neu-Ableitung des Wrapping-Keys bei Passwortänderung
  • Sub-Resource-Integrity für WASM — SRI-Hash-Prüfung des Argon2-WASM-Binaries

Open für Sicherheitsforscher: Wenn du Sicherheitslücken in der Implementierung findest, melde sie bitte vertraulich an security@zotto.me. Wir nehmen Security-Reports ernst und bemühen uns um schnelle Reaktionszeiten.