barox transfer ist Zero-Knowledge: Die gesamte Ver- und
Entschlüsselung läuft im Browser (libsodium / WebAssembly). Der Server speichert
ausschließlich Chiffrate und für die jeweiligen Empfänger gewickelte
Schlüssel – niemals Klartext, Dateinamen, Passphrasen oder entschlüsselte
Schlüssel. Selbst bei vollständiger Server-Kompromittierung bleibt der Inhalt
unlesbar.
Jeder Transfer bekommt einen frischen, symmetrischen
File-Key (32 Byte, rein zufällig). Dieser eine
Schlüssel verschlüsselt sowohl die Dateien als auch die Metadaten und wird dann
für jeden Empfänger einzeln gewickelt:
File-Key (32 B, zufällig, pro Transfer)
│
├─ verschlüsselt die Datei-Chunks → crypto_secretstream (XChaCha20-Poly1305)
├─ verschlüsselt die Metadaten → crypto_secretbox (Namen, Größen, Nachricht)
│
└─ wird pro Empfänger gewickelt:
├─ intern → crypto_box_seal( File-Key , Public-Key des Empfängers )
└─ extern → secretbox( File-Key , Argon2id(Passwort) )
Identitäts-Schlüsselpaar (X25519, pro Nutzer)
├─ Public-Key → liegt im Klartext auf dem Server (für Versiegelung an den Nutzer)
└─ Private-Key → NIE im Klartext auf dem Server; doppelt gewickelt:
├─ secretbox( Private-Key , Argon2id(Passphrase) )
└─ secretbox( Private-Key , Argon2id(Recovery-Code) )
Kernidee: Der File-Key existiert im Klartext nur kurz im Browser.
Auf dem Server liegt er ausschließlich gewickelt – entweder hinter dem
privaten Schlüssel des internen Empfängers oder hinter dem out-of-band geteilten
Passwort des externen Empfängers.
3 · Schlüssel-Setup eines internen Nutzers Client
Microsoft 365 / Azure AD authentifiziert den Nutzer, liefert aber kein
Schlüsselmaterial für die Verschlüsselung. Deshalb legt jeder Nutzer einmalig eine
separate Passphrase fest, aus der lokal sein Schlüsselpaar
abgeleitet/geschützt wird.
BrowserServer
1. Schlüsselpaar erzeugen (X25519)
2. Recovery-Code würfeln (5×4 Zeichen)
3. Private-Key 2× wickeln:
encP = secretbox(priv, Argon2id(Passphrase, saltP))
encR = secretbox(priv, Argon2id(Recovery, saltR))
4. senden: public_key, encP, saltP,
encR, saltR ──────────────▶ speichert nur diese Werte
(Private-Key bleibt im Browser)
5. Recovery-Code dem Nutzer
anzeigen (nur dieses eine Mal)
Browser (Absender)Server
1. File-Key = secretstream_keygen()
2. Metadaten {Namen, Größen, Nachricht}
→ encMetadata = secretbox(meta, File-Key)
3. pro Empfänger-E-Mail:
lookup(email) ──────────────▶ Public-Key bekannt?
◀────────────── ja → public_key │ nein → not found
┌─ intern: wrapped = box_seal(File-Key, public_key)
└─ extern: pw = randomPassword(18)
wrapped = secretbox(File-Key, Argon2id(pw, salt))
4. Transfer anlegen (encMetadata,
Empfängerliste mit wrapped_key) ────────────▶ speichert Chiffrat-Hülle + wrapped keys
5. Dateien chunkweise verschlüsseln:
header + secretstream(chunk) ──────────────▶ legt Chunks ab (Bytes, opak)
6. complete ────────────▶ versendet Benachrichtigungs-E-Mails
(intern: Link · extern: nur Link)
7. externes Passwort dem Absender
anzeigen → out-of-band an Empfänger
Interner Empfänger
File-Key wird anonym an den Public Key versiegelt
(crypto_box_seal). Nur der zugehörige private Schlüssel –
also der Empfänger mit seiner Passphrase – kann ihn öffnen.
Externer Empfänger
Die App würfelt ein starkes Zufallspasswort. Der File-Key
wird mit Argon2id(Passwort) verschlüsselt. Das Passwort
erreicht den Server nie – der Absender teilt es out-of-band
(Telefon/SMS) mit.
Quelle: public/assets/js/send.js
5 · Datei-Streaming & großer Dateien
Dateien werden nicht am Stück, sondern gechunkt verschlüsselt
(crypto_secretstream_xchacha20poly1305). Das hält den Speicherbedarf
niedrig, erlaubt resumierbaren Upload/Download und authentifiziert jeden Chunk
einzeln – inklusive Reihenfolge und korrektem Stream-Ende.
Datei → Klartext-Chunks à CHUNK_BYTES (Default 4 MB)
Chunk 0: [ header (24 B) ][ secretstream(chunk0, TAG_MESSAGE) ]
Chunk 1: [ secretstream(chunk1, TAG_MESSAGE) ]
…
Chunk n: [ secretstream(chunkN, TAG_FINAL) ] ← markiert das Ende
Pro Chunk +17 B Auth-Tag (ABYTES). Empfänger liest exakte Frames
(CHUNK_BYTES + 17) und entschlüsselt streamend auf die Platte.
Server-Limit:CHUNK_BYTES (Default 4 MB)
muss kleiner als PHP post_max_size bleiben. Die Klartext-Chunkgröße
steckt verschlüsselt in den Metadaten, damit der Empfänger die Frame-Grenzen kennt.
Ein Mitarbeiter lädt einen Kunden über einen tokengesicherten Link
(/u/{token}) zum Hochladen ein. Der Kunde braucht kein Konto.
Seine Dateien werden im Browser verschlüsselt und an den öffentlichen
Schlüssel des Mitarbeiters versiegelt – nur dieser kann sie später mit
seiner Passphrase öffnen.
Browser (Kunde, ohne Konto)Server
1. Einladung öffnen ──────────────▶ liefert Public-Key des Mitarbeiters
2. File-Key erzeugen, Dateien verschlüsseln
3. wrapped = box_seal(File-Key, pub Mitarbeiter)
4. Upload (Chunks + wrapped) ──────────────▶ erscheint als Transfer unter /t/{uuid}
Beim Setup erhält der Nutzer einen einmaligen Recovery-Code, der denselben
privaten Schlüssel separat wickelt. Vergisst er seine Passphrase, entsperrt er den
privaten Schlüssel mit dem Code und setzt eine neue Passphrase – ohne dass das
Zero-Knowledge-Prinzip bricht.
Kein Hintertürchen: Gehen Passphrase und Recovery-Code
verloren, sind die empfangenen Daten unwiederbringlich verloren. Das ist eine
bewusste Eigenschaft von Zero-Knowledge – der Server kann nicht aushelfen.
Quelle: keys-setup.js → bindRecover
9 · Was der Server speichert – und was nie
✅ Auf dem Server (alles opak)
Datei-Chiffrate (außerhalb des Webroots)
Gewickelte File-Keys pro Empfänger
Verschlüsselte Metadaten (Namen, Nachricht)
Public Keys der Nutzer
Gewickelter Private-Key (encP/encR + Salts)
Empfänger-E-Mails, Größen, Zeitstempel, Audit-Log
⛔ Niemals auf dem Server
Datei-Klartext oder Klartext-Dateinamen
File-Keys im Klartext
Passphrasen, Recovery-Codes
Externe Passwörter (out-of-band)
Entschlüsselte private Schlüssel
Auslieferung der Chiffrate erfolgt nur über PHP mit Zugriffsprüfung, Ablauf- und
Download-Limits; zusätzlich HTTPS/HSTS, strenge CSP (script-src 'self'),
CSRF-Schutz und Rate-Limiting der externen Endpunkte.
10 · Restrisiken & bewusste Entscheidungen
Metadaten sichtbar: Empfänger-E-Mail-Adressen sowie
Transfer-Metadaten (Größen, Zeitpunkte) kennt der Server – nötig für
Benachrichtigung und Zugriffssteuerung.
Verlust = Datenverlust: Ohne Passphrase und
Recovery-Code sind empfangene Daten verloren.
Externe Sicherheit: hängt an der Stärke des app-generierten
Passworts und der getrennten Verteilung von Link (E-Mail) und Passwort
(out-of-band).
Auslieferung des Codes: Da die Krypto im Browser läuft, muss
dem ausgelieferten JavaScript vertraut werden – darum strikte CSP und HTTPS.