Wie Ihr Tresor verschlüsselt

Diese Seite beschreibt, was in Ihrem Browser passiert, bevor Dateien Ihren Rechner verlassen. Der Code, der das tut, wird mitsamt Sourcemaps ausgeliefert: in den Entwicklerwerkzeugen unter „Sources" steht crypto.js Zeile für Zeile so da, wie er im Repository liegt. Wie Sie das prüfen, steht unter Quelltext nachprüfen.

  • Hier dokumentiert: Schlüsselableitung, Dateiverschlüsselung, Dateiindex & Sync, öffentliche API-Schnittstellen
  • Nur Überblick: interne Server-Implementierung und Betriebsdetails
  • Nie über das Netz: Ihr Tresor-Passwort, Ihre Recovery-Phrase, der unverschlüsselte Master-Schlüssel, unverschlüsselte Dateiinhalte
  • Auf dem Server: Salts, verschlüsselte Hüllen (passwordWrapped, recoveryWrapped) und ein öffentlicher Ed25519-Schlüssel, mit dem sich ein Passwort-Reset nur prüfen, nicht unterschreiben lässt

Das Prinzip

Der Server speichert nur verschlüsselte Dateiinhalte und technische Hüllen (Salts, Wrapped Keys). Ihr Master-Schlüssel entsteht beim ersten Setup in Ihrem Browser und wird dort in Hüllen verpackt, bevor etwas an den Server geht. Beim Entsperren wird er ausschließlich lokal wieder freigegeben. Ohne Ihr Passwort oder Ihre Recovery-Phrase können Inhalte nicht gelesen werden, auch nicht durch den Betreiber.

1 Sie geben Passwort oder Recovery ein (nur lokal)
→
2 Browser entschlüsselt die Hülle → Master-Key im RAM
→
3 AES-GCM lokal für Dateien
→
4 Nur Ciphertext geht raus

Ersteinrichtung Ihres Tresors

Beim ersten Öffnen legen Sie Passwort und Recovery-Phrase fest. Intern passiert Folgendes:

  1. Sie wählen ein Passwort (mindestens 12 Zeichen).
  2. Der Browser erzeugt zufällige Salts und würfelt 32 Master-Bytes per CSPRNG (generateMasterBits()).
  3. Diese Bytes werden unter Ihrer Recovery-Phrase versiegelt (Wrap).
  4. Dieselben Bytes werden unter Ihrem Passwort separat versiegelt.
  5. Aus denselben Bytes leitet der Browser per HKDF zwei weitere Schlüssel ab: einen für den lokalen Dateiindex und ein Ed25519-Paar für den Löschnachweis.
  6. Nur die Hüllen, die Salts und zwei öffentliche Schlüssel werden an den Server gesendet: nie das Passwort, nie die Recovery-Wörter, nie der Master-Schlüssel im Klartext.

passwordWrapped ist dabei nicht Ihr Passwort in verschlüsselter Form, sondern der Master-Schlüssel (32 Byte), mit einem vom Passwort abgeleiteten Schlüssel versiegelt.

src/crypto.js · Master-Key unter Recovery versiegeln
export async function wrapMasterBits(masterBits, kek) {
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
  const ct = await globalThis.crypto.subtle.encrypt(
    { name: "AES-GCM", iv }, kek, masterBits
  );
  return { wrappedHex: toHex(new Uint8Array(ct)), ivHex: toHex(iv) };
}

// Das ist alles, was beim Setup zum Server geht. Vollstaendig, nicht "unter anderem":
// salt, recoverySalt, recoveryWrapped, recoveryIv,
// recoveryProofKey (oeffentlich), recoveryProofAlg,
// passwordWrapped, passwordIv,
// deleteProofKey (oeffentlich)

Schlüsselerzeugung

Beim ersten Setup würfelt der Browser 32 zufällige Master-Bytes (CSPRNG). Aus Ihrem Tresor-Passwort wird per PBKDF2-SHA256 mit 600.000 Iterationen nur der Schlüssel abgeleitet, der diese Bytes in passwordWrapped versiegelt. Beim Entsperrenleitet der Browser aus Ihrem Passwort einen KEK ab, um die Hülle lokal zu öffnen. Der Session-Schlüssel ist extractable: false und verlässt den Arbeitsspeicher nicht in lesbarer Form.

src/crypto.js · Master-Key erzeugen & KEK ableiten
export function generateMasterBits() {
  return globalThis.crypto.getRandomValues(new Uint8Array(32));
}

const PBKDF2_ITERATIONS = 600000;

export async function deriveKekFromMnemonic(mnemonic, recoverySalt) {
  const enc = new TextEncoder();
  const keyMaterial = await globalThis.crypto.subtle.importKey(
    "raw", enc.encode(mnemonic.trim()), { name: "PBKDF2" }, false, ["deriveKey"]
  );
  return await globalThis.crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt: enc.encode(recoverySalt),
      iterations: PBKDF2_ITERATIONS,
      hash: "SHA-256"
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,  // extractable: false, Schlüssel bleibt im RAM
    ["encrypt", "decrypt"]
  );
}

// Der Passwort-Kanal nutzt denselben Umschlag, nur mit dem Passwort-Salt.
export async function deriveKekFromPassword(password, salt) {
  return deriveKekFromMnemonic(password, salt);
}

Passwort-Verifikation & Recovery-Phrase

Ihr Tresor-Passwort wird weder gespeichert noch an den Server übertragen. Der Server erhält und behält nur eine verschlüsselte Hülle: den Master-Schlüssel, verpackt mit AES-GCM unter einem Schlüssel, der aus Ihrem Passwort und einem Salt abgeleitet wird. Erst wenn Sie im Browser Ihr Passwort eingeben, kann diese Hülle geöffnet und der Master-Schlüssel im RAM genutzt werden.

DatenGeht an den Server?Liegt auf dem Server?
Tresor-Passwort (Klartext)NeinNein
Recovery-Phrase (Klartext)NeinNein
Master-Schlüssel (Klartext)NeinNein
salt (Passwort-Kanal)Ja (beim Setup)Ja (öffentlich, kein Geheimnis)
passwordWrapped + passwordIvJa (beim Setup)Ja (verschlüsselter Master-Key)
recoveryWrapped + recoveryIv + recoverySaltJa (beim Setup)Ja (verschlüsselter Master-Key, Recovery-Kanal)
recoveryProofKey (öffentlicher Ed25519-Schlüssel)Ja (beim Setup)Ja (prüft Passwort-Reset-Anfragen, kann selbst nichts unterschreiben und nichts entschlüsseln)
deleteProofKey (öffentlicher Ed25519-Schlüssel)Ja (beim Setup)Ja (prüft Löschanfragen, kann selbst nichts unterschreiben und nichts entschlüsseln)
Schlüssel je Datei (Klartext)NeinNein (nur mit dem Master-Schlüssel verschlüsselt, im Journal)

Ersteinrichtung (Setup)

  1. Sie geben Ihr Passwort ein (bleibt im Browser).
  2. Der Browser erzeugt salt, würfelt 32 Master-Bytes (CSPRNG) und leitet den KEK aus dem Passwort ab (PBKDF2).
  3. Diese Bytes werden mit einem passwort-abgeleiteten Schlüssel versiegelt → passwordWrapped.
  4. Parallel dasselbe für die Recovery-Phrase → recoveryWrapped.
  5. Salts, Hüllen und der öffentliche Ed25519-Schlüssel gehen an POST /api/tresor/setup. Der zugehörige private Schlüssel wird bei Bedarf neu aus Ihren 12 Wörtern abgeleitet und nirgends gespeichert.

Entsperren (Unlock)

  1. Der Browser lädt salt, passwordWrapped und passwordIv vom Server.
  2. Sie geben Ihr Passwort ein (nur lokal, nie im Request).
  3. Daraus wird ein Schlüssel abgeleitet, die Hülle entschlüsselt, der Master-Key importiert.
  4. Erst jetzt kann der Tresor Dateien entschlüsseln. Falsches Passwort → AES-GCM schlägt fehl, kein Fallback.
src/crypto.js · Entsperren per Passwort (vollständig lokal)
export async function unlockWithPassword(password, salt, passwordWrapped, passwordIv) {
  if (!passwordWrapped || !passwordIv) {
    throw new Error('PASSWORD_ENVELOPE_MISSING');
  }
  // password bleibt im Browser; Server sieht es nie
  const kek = await deriveKekFromPassword(password, salt);
  const masterBits = await unwrapMasterBits(passwordWrapped, passwordIv, kek);
  return importMasterKeyFromBits(masterBits);  // sessionKey, nur RAM
}

Ihre 12-Wörter Recovery-Phrase folgt BIP39 (128 Bit Entropie + Prüfsumme) und funktioniert über einen separaten Kanal (recoverySalt, recoveryWrapped). Auch hier gilt: die Wörter verlassen Ihren Browser nicht. Auf dem Server liegen nur die verschlüsselte Hülle und ein daraus abgeleiteter öffentlicherEd25519-Schlüssel, mit dem er Reset-Anfragen prüfen kann.

src/crypto.js · Recovery-Mnemonic erzeugen (Auszug)
export async function generateMnemonic() {
  const entropy = globalThis.crypto.getRandomValues(new Uint8Array(16));
  const hash = new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', entropy));
  // 128 Bit Entropie + 4 Bit Pruefsumme aus dem ersten Hash-Byte -> 132 Bit,
  // in Bloecken zu 11 Bit auf die 2048 Woerter der BIP39-Liste abgebildet.
  return words.join(' ');
}

Stellt die Recovery-Phrase mein Passwort wieder her?

Nein. Ihr Tresor-Passwort wird nirgends gespeichert, weder im Klartext noch verschlüsselt. Die Recovery-Phrase ist kein „Passwort-Knacker“, sondern ein zweiter Schlüssel zum gleichen Master-Schlüssel. Beim Setup werden dieselben 32 Master-Bytes einmal unter Ihrem Passwort und einmal unter Ihrer Phrase verpackt (passwordWrapped und recoveryWrapped). Wer die Phrase kennt, kann den Tresor öffnen, genauso wie mit dem Passwort, aber daraus lässt sich Ihr Passwort nicht rekonstruieren.

Passwort zurücksetzen (mit Recovery-Phrase)

Wenn Sie Ihr Passwort vergessen haben, aber Ihre 12 Wörter noch haben, können Sie ein neues Passwort setzen, ohne Ihre Dateien neu zu verschlüsseln:

  1. Sie geben die Recovery-Phrase lokal ein (sie verlässt den Browser nicht).
  2. Der Browser entschlüsselt recoveryWrapped → Sie erhalten wieder den gleichen Master-Schlüssel.
  3. Sie wählen ein neues Passwort; daraus wird ein neuer KEK abgeleitet.
  4. Derselbe Master-Schlüssel wird unter dem neuen Passwort neu verpackt. Zusätzlich unterschreibt der Browser eine vom Server ausgegebene Einmal-Aufgabe mit dem Ed25519-Schlüssel aus Ihren 12 Wörtern. Unterschrieben wird dabei auch die neue Hülle, eine abgefangene Unterschrift lässt sich also nicht mit anderem Material weiterverwenden.
  5. Neues passwordWrapped plus Challenge-Proof gehen an POST /api/tresor/reset-password.

Entsperren, Sperren, Session

Nach dem Entsperren liegt Ihr Master-Schlüssel nur im Arbeitsspeicher der Browser-Sitzung (sessionKey). Er wird weder in localStorage noch in IndexedDB abgelegt. Vorher lag er ausschließlich als verschlüsselte Hülle auf dem Server; freigegeben wird er erst durch Ihre lokale Passwort- oder Recovery-Eingabe (siehe Abschnitt Passwort & Recovery).

Wenn Sie „Tresor sperren“ wählen, wird der Schlüssel aus dem RAM entfernt und die Dateiliste ausgeblendet. Ihr Datenputzer-Login bleibt bestehen, Sie müssen sich nicht erneut bei datenputzer.de anmelden.

Zwei Ebenen sind zu unterscheiden:

  • Datenputzer-Login (HttpOnly-Cookie): Zugang zum Konto und Tarif
  • Tresor entsperrt (Schlüssel im RAM): Zugriff auf verschlüsselte Inhalte

Der Schlüssel im Arbeitsspeicher

Verschlüsselung nützt wenig, wenn der Schlüssel danach stundenlang im Arbeitsspeicher liegt. Die entscheidende Frage ist deshalb nicht nur ob entschlüsselt wird, sondern wann und wie lange. Was der Tresor dazu tut:

FrageIm Tresor
Wann wird der Master-Schlüssel freigelegt?Erst wenn Sie Ihr Passwort oder Ihre 12 Wörter eingeben. Ohne Ihre Eingabe passiert nichts, auch nicht beim Öffnen der Seite.
Wie lange liegt er im Arbeitsspeicher?Bis Sie sperren, die Auto-Sperre greift oder die Tab-Sperre auslöst. Danach wird er verworfen.
Landet er auf der Festplatte?Nein. Auf dem Server liegen nur die Hüllen (passwordWrapped, recoveryWrapped und je Passkey eine weitere), lokal nichts. Weder localStorage noch IndexedDB bekommen ihn zu sehen.
Lässt sich das an Hardware binden?Teilweise: Mit einem Passkey hängt das Entsperren am Gerät (Secure Enclave, TPM, Sicherheitsschlüssel). Der entsperrte Schlüssel selbst liegt danach wie immer im Arbeitsspeicher des Browsers.

Dass diese Fragen nicht theoretisch sind, zeigte im Mai 2026 ein Fall bei Microsoft Edge: der Browser entschlüsselte gespeicherte Passwörter schon beim Start und hielt sie danach dauerhaft im Arbeitsspeicher, ohne dass jemand danach gefragt hatte.

Wogegen der Tresor schützt und wogegen nicht

Eine Tabelle, in der überall „geschützt" steht, ist keine Sicherheitsdokumentation, sondern Werbung. In zwei Zeilen steht deshalb, dass es keinen technischen Schutz gibt.

BedrohungSchutz im TresorIhre Maßnahme
Server liest DateiinhalteEs liegt nur Kryptotext dort, der Schlüssel entsteht bei IhnenEin langes, ungewöhnliches Passwort wählen: gegen die Hülle ist Raten offline möglich
Wir liefern manipuliertes JavaScript ausTechnisch keiner. Wer den Code ausliefert, der verschlüsselt, könnte ihn ändern. Das gilt für jeden Anbieter, dessen Krypto im Browser läuftQuelltext und Prüfsummen vergleichen (nachprüfen). Die Prüfwerte jedes ausgelieferten Bundles stehen offen unter /pruefwerte.json, zu den meisten Ständen liegt zusätzlich eine Kopie im Internet Archive, also außerhalb unserer Reichweite
Abhörung im NetzTLS, und die Blöcke sind schon vorher verschlüsseltNichts nötig
Schadsoftware auf Ihrem Rechner oder eine bösartige Browser-ErweiterungKeiner, solange der Tresor offen ist. Wer Ihren Rechner oder Ihren Browser kontrolliert, sieht, was Sie tippen und was der Tresor anzeigt. Das gilt für jede Anwendung, nicht nur für uns. Was wir tun: der Schlüssel ist nicht auslesbar und liegt nur im Arbeitsspeicher, Dateinamen liegen auf dem Gerät verschlüsselt, Kopieren aus Passwortfeldern ist gesperrt, der Tresor sperrt sich nach Inaktivität selbstSperren, wenn Sie fertig sind. Nur Erweiterungen aus vertrauenswürdiger Quelle, möglichst wenige. Auf einem befallenen Gerät den Tresor gar nicht erst öffnen
Fremder Code im Tresor-Fenster (XSS)Teilweise. Die CSP erlaubt nur eigene Skripte, keine Inline-Skripte und keine Inline-Styles; mit Trusted Types nimmt der Browser zusätzlich nur Skriptadressen vom eigenen Ursprung an. Der Schlüssel ist extractable: false, lässt sich also nicht auslesen und mitnehmen. Benutzen kann fremder Code ihn trotzdem, solange der Tresor offen istSperren, wenn Sie fertig sind. Kurze Auto-Sperre einstellen
Jemand setzt sich an Ihr offenes GerätSperre und optionale Tab-SperreKurze Auto-Sperre, Gerät beim Weggehen verriegeln
Sie verlieren Passwort und die 12 WörterKeiner. Wir können den Tresor nicht öffnenDie 12 Wörter auf Papier, an einem zweiten Ort, und jemandem sagen, dass es sie gibt

Der Tresor im Datenputzer-Dashboard

Im Dashboard erscheint der Tresor als eigener Bereich. Technisch läuft er dort weiter auf seiner eigenen Adresse tresor.datenputzer.de, eingebettet als Rahmen. Das ist Absicht: Das Dashboard lädt Dienste wie die Zahlungsabwicklung und den Google-Login. Durch die Trennung kann kein Skript des Dashboards in den Tresor hineingreifen, Ihren Schlüssel benutzen oder Dateinamen lesen.

  • Was zwischen Dashboard und Tresor ausgetauscht wird: nur Bedienung. Der Tresor meldet „gesperrt oder offen“, belegten Platz als Zahl und seine Höhe; das Dashboard schickt das Farbschema und darf den Tresor sperren, etwa beim Abmelden. Beide Seiten prüfen den Absender exakt.
  • Was nie ausgetauscht wird: Schlüssel, Passwort, Datei- und Ordnernamen, Inhalte, unterschriebene Adressen. Das Dashboard kann im Tresor nichts auslösen außer Sperren.
  • Wer den Tresor einbetten darf: nur datenputzer.de, und nur über die eigens dafür gebaute Adresse /einbettung. Alle anderen Tresor-Seiten lassen sich nicht einbetten.
  • Der Tresor bleibt auch direkt unter tresor.datenputzer.de erreichbar, mit demselben Konto und denselben Dateien.

Nach dem Abo-Ende und bei zu viel belegtem Speicher

  • Abo beendet: Ihr Tresor wird nicht sofort gelöscht. Sie können Ihre Dateien weiter entsperren, ansehen und herunterladen, aber nichts Neues hochladen. Für das Herunterladen gilt ein Kontingent, das für einen vollständigen Export reicht. Nach einer Frist, die in den AGB steht, werden die Dateien gelöscht. Mit einem neuen Abo bleibt alles, wie es ist.
  • Mehr belegt als gebucht (zum Beispiel nach dem Kündigen von Zusatzspeicher): Neue Uploads sind gesperrt, bis Sie Dateien löschen oder Speicher erweitern. Bleibt die Überbelegung bis zum Ende der Frist bestehen, entfernen wir nur den Überschuss, beginnend mit den zuletzt hochgeladenen Dateien. Der gebuchte Teil bleibt unangetastet.
  • Wie wir Sie informieren: im Tresor und im Dashboard, dazu wenige E-Mails, die Sie abbestellen können. Die E-Mails nennen Anzahl und Größe der Dateien, keine Namen: die kennt nur Ihr Browser. Im Tresor sehen Sie, welche Dateien betroffen sind.

Einstellungen für mehr Sicherheit

Unter Einstellungen → Sicherheit im Tresor können Sie Folgendes konfigurieren:

  • Automatisch sperren nach … (5 / 15 / 30 / 60 Minuten oder Aus): Bei Inaktivität wird der Master-Schlüssel aus dem RAM entfernt. Upload-Aktivität zählt als Nutzung.
  • Sperren während Uploads: „Laufende Uploads beenden“ hält den Schlüssel kurz für den aktiven Job; „Sofort stoppen (streng)“ löscht ihn sofort und pausiert die Queue.
  • Sperren wenn Tab im Hintergrund: Optional, standardmäßig aus. Sperrt beim Tab-Wechsel (Paranoia-Modus).
  • Passkey: schützt vor Mitlesen beim Tippen und vor gefälschten Seiten (er funktioniert nur auf tresor.datenputzer.de), und seine Hülle lässt sich nicht erraten. Er schützt nicht vor Schadsoftware, die den bereits geöffneten Tresor beobachtet.
src/securityPrefs.js · localStorage-Schlüssel
tresor_auto_lock_minutes      // 0 | 5 | 15 | 30 | 60
tresor_lock_upload_policy      // strict | finish_current
tresor_lock_on_tab_hidden      // 0 | 1

Dateiverschlüsselung (AES-GCM)

Ihre Dateien werden in 5-MiB-Chunks geteilt. Jeder Chunk erhält eine frische 12-Byte-IV. Zusätzliche authentifizierte Daten (AAD) binden den Ciphertext an eine Datei-UUID und den Chunk-Index, damit Stücke nicht vertauscht werden können.

Seit Oktober 2026 hat jede neue Datei einen eigenen Schlüssel: 256 Bit aus dem Zufallsgenerator, nur für diese eine Datei. Der Master-Schlüssel verschlüsselt nicht mehr die Datei selbst, sondern nur diesen Dateischlüssel (createFileKey). Die Hülle ist per AAD an die Datei-UUID gebunden und lässt sich nicht auf eine andere Datei umhängen. Sie reist verschlüsselt im Änderungsjournal mit, so kennt jedes Ihrer Geräte sie. Ältere Dateien bleiben unverändert lesbar, bei ihnen ist der Master-Schlüssel weiter direkt der Dateischlüssel.

src/crypto.js · Schlüssel je Datei (Auszug)
export async function createFileKey(masterKey, encryptionId) {
    const bits = globalThis.crypto.getRandomValues(new Uint8Array(32));
    try {
        const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
        const ct = await globalThis.crypto.subtle.encrypt(
            { name: "AES-GCM", iv, additionalData: dekAad(encryptionId) }, masterKey, bits
        );
        …
        return { fileKey, wrappedFileKey: bytesToB64(combined) };
    } finally {
        bits.fill(0);
    }
}
src/crypto.js · Ein Chunk verschlüsseln
export async function encryptChunk(arrayBuffer, key, fileId, chunkIndex) {
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
  const aad = new TextEncoder().encode(`${fileId}:${chunkIndex}`);

  const encryptedContent = await globalThis.crypto.subtle.encrypt(
    { name: "AES-GCM", iv, additionalData: aad },
    key,
    arrayBuffer
  );
  const combined = new Uint8Array(iv.length + encryptedContent.byteLength);
  combined.set(iv);
  combined.set(new Uint8Array(encryptedContent), iv.length);
  return combined;
}

Download und Entschlüsselung

Klicken Sie eine Datei an, holt der Browser kurzlebige, unterschriebene Adressen von unserem Server, mehrere Blöcke mit einer Anfrage, und lädt die Blöcke damit direkt vom Objektspeicher, bis zu vier gleichzeitig. Unser Server sieht den Kryptotext dabei nicht, er stellt nur die Adressen aus. Entschlüsselt wird anschließend lokal mit dem Schlüssel der Datei.

Dateien ab 256 MB laufen im Strom auf die Festplatte: Ihr Browser entschlüsselt Block für Block und reicht jeden Block an einen kleinen Download-Helfer (/herunterladen/sw.js, ein Service Worker auf derselben Adresse). Der gibt die Blöcke als normalen Download weiter. So liegt nie die ganze Datei im Arbeitsspeicher, und auch sehr große Dateien lassen sich im Dashboard herunterladen. Der Helfer kennt keinen Schlüssel, macht keine Netzwerkanfragen und speichert nichts. In Safari, wo das nicht zuverlässig klappt, bleibt der bisherige Weg.

src/crypto.js · Streaming-Entschlüsselung (Auszug)
export async function decryptBlobStream(encryptedBlob, key, originalType, fileId) {
  const combinedArray = new Uint8Array(await encryptedBlob.arrayBuffer());
  const stream = new ReadableStream({
    async pull(controller) {
      // Chunkweise decryptChunk -> enqueue -> naechster Chunk.
      // Kein Array, das die ganze Datei im Klartext sammelt.
    }
  });
  // new Blob([stream]) waere falsch: ein ReadableStream ist kein gueltiger Blob-Teil
  // und wuerde zu "[object ReadableStream]" verstringlicht, also 23 Byte Schrott.
  // Response liest den Strom korrekt aus.
  const streamed = await new Response(stream).blob();
  return originalType ? new Blob([streamed], { type: originalType }) : streamed;
}

Scheitert die Entschlüsselung (falsches Passwort, beschädigte Daten, manipulierter Chunk), bricht der Vorgang mit einer Fehlermeldung ab.

Dateiindex: lokal und in der Cloud

Ihr Dateiindex (Ordner, Dateinamen, Struktur) existiert auf zwei Ebenen: schnell lokal in IndexedDB und verschlüsselt synchronisiert auf dem Server im Änderungsjournal.

Lokal: IndexedDB (Dexie)

Nach dem Entsperren liegen Ordner und Dateimetadaten in Dexie/IndexedDB unter TresorDatabase auf Ihrem Gerät. So können Sie sortieren, suchen und Dateien auch offline öffnen, ohne bei jedem Klick den Server zu fragen.

src/db.js · Schema (v5, Auszug)
db.version(5).stores({
  folders: '++id, clientId, parentClientId, parentId, name, createdAt',
  files: '++id, clientId, folderClientId, folderId, name, encryptionId, uploadId, ...',
  fileBlobs: 'id',
  pendingUploads: 'id, fileName, uploadId, uploadedChunks, totalChunks',
  meta: 'key'  // journalLastEventId, journalDeviceTag,
               // journalDeviceSeq, journalBurnedSeq
});
  • files: sealed (Name, Typ, Größenangabe verschlüsselt), Ordner, Größe in Byte, encryptionId (UUID für AAD), wrappedFileKey, uploadId für Cloud-Chunks
  • fileBlobs: bereits heruntergeladene Dateien, nur als Chiffretext
  • pendingFileBlobs: seit Oktober 2026 leer und ungenutzt. Bis dahin lag hier für die Fortsetzung nach dem Neuladen eine unverschlüsselte Kopie der Datei, das ist abgeschafft.
  • Maximal 64 Ordner pro Tresor (MAX_VAULT_FOLDERS)

Cloud: verschlüsseltes Journal

Für Geräteübergreifenden Sync schreibt der Browser jede Änderung (Ordner anlegen, Datei hinzufügen, umbenennen, verschieben, löschen) als Event ins Journal. Das Event wird mit dem Master-Schlüssel per AES-GCM verschlüsselt und als encrypted_payload an POST /api/tresor/journal gesendet. Auf dem Server liegt nur der Ciphertext in Änderungsjournal, keine Klartext-Dateinamen.

  1. Beim Entsperren lädt syncJournalFromServer() neue Events (GET /api/tresor/journal?since=…).
  2. Jedes Event wird im Browser mit dem Master-Schlüssel entschlüsselt.
  3. Die Klartext-Metadaten werden in IndexedDB übernommen (applyJournalEvent).
src/journal.js · Event verschlüsseln und syncen (Auszug)
const encrypted_payload = await encryptJournalPayload(masterKey, {
  type: 'file_add', name: '…', parentClientId: '…', uploadId: '…', …
});
// → POST /api/tresor/journal

const event = await decryptJournalPayload(masterKey, row.encrypted_payload);
await applyJournalEvent(event);  // → IndexedDB files/folders

Warum Verschlüsseln allein hier nicht reicht

Jeder Eintrag ist für sich verschlüsselt und mit einem Prüfwert versehen, aber das bindet nur seinen Inhalt, nicht seine Position. Ein Server könnte einzelne Einträge weglassen oder umsortieren: eine Datei fehlte dann still in der Liste, oder eine alte Umbenennung gewänne gegen eine neue. Lesen könnte er trotzdem nichts, aber ein Safe, dessen Inhaltsverzeichnis sich von außen kürzen lässt, ist kein guter Safe.

Deshalb trägt jeder Eintrag im verschlüsselten Teil eine Gerätekennung und einen je Gerät fortlaufenden Zähler. Beides steht damit unter demselben Prüfwert wie der Inhalt und lässt sich nicht fälschen. Beim Abgleich müssen die Zähler eines Geräts eine lückenlose Reihe ab 1 ergeben. Fehlt eine Zahl, sagt der Tresor das.

  • Je Gerät, nicht global: ein globaler Zähler würde kollidieren, wenn zwei Ihrer Geräte gleichzeitig schreiben. Ein echter Konflikt sähe dann aus wie ein Angriff.
  • Der Tresor sperrt bei einem Fund nicht. Ihre Dateien sind unversehrt, nur die Liste ist womöglich unvollständig. Wer an dieser Stelle ausgesperrt würde, käme an gar nichts mehr heran, obwohl alles noch da ist.
  • Was das nicht erkennt: was ein Gerät nie gesehen hat, kann es nicht vermissen. Unterschlägt der Server die jüngsten Einträge eines anderen Geräts, fällt das hier nicht auf. Die eigenen fehlenden Einträge erkennt jedes Gerät.
  • Einträge aus der Zeit vor dieser Prüfung tragen keinen Zähler und werden davon ausgenommen, sonst schlüge jeder bestehende Tresor sofort Alarm.

Upload

  1. Sie wählen oder ziehen eine Datei in den Tresor.
  2. Der Browser teilt sie in 5-MiB-Stücke und verschlüsselt jedes Stück.
  3. Sie sehen Fortschritt: Vorbereiten → Verschlüsseln → Übertragen → Abschließen
  4. Für die Stücke holt der Browser kurzlebige, unterschriebene Adressen, mehrere mit einer Anfrage, und lädt den Kryptotext per PUT direkt zum Objektspeicher. Wie viele Stücke gleichzeitig unterwegs sind, richtet sich nach Ihrer Leitung: drei zu Beginn, bis zu sechs, solange es schneller wird. Unser Server bekommt die Bytes nie zu sehen, er stellt nur die Adressen aus und zählt den belegten Platz. Nach POST /api/tresor/upload/complete ist die Datei gesichert.
src/uploads.js · Chunk verschlüsseln und übertragen (Auszug)
export const CHUNK_SIZE = 5 * 1024 * 1024;

const chunkBuffer = await file.slice(sliceStart, sliceEnd).arrayBuffer();
encryptedChunk = await encryptChunk(chunkBuffer, chunkKey, fileUuid, c);

// Kurzlebige unterschriebene Adresse von unserem Server holen ...
const { url } = await (await authenticatedFetch(`${API_URL}/upload/sign`, {
  method: 'POST', body: JSON.stringify({ uploadId, chunkIndex: c })
})).json();
// ... und den Kryptotext direkt zum Objektspeicher schieben.
// Bewusst ohne Authorization-Kopf: die Adresse IST der Ausweis.
await fetch(url, { method: 'PUT', body: chunkBody });

Löschen nur mit offenem Tresor

Wer Ihre Anmeldung stiehlt, etwa über Ihr Postfach, kann Ihre Dateien nicht lesen. Ohne weiteren Schutz könnte er sie aber in den Papierkorb werfen, den der Server nach 30 Tagen leert, oder sofort endgültig löschen. Deshalb verlangt der Server für jede Löschung eine Unterschrift.

  1. Beim Einrichten leitet der Browser aus dem Master-Schlüssel ein Ed25519-Schlüsselpaar ab und schickt nur den öffentlichen Teil mit (deleteProofKey).
  2. Vor jedem Löschen holt der Browser eine einmalige Aufgabe (POST /api/tresor/delete-challenge, 2 Minuten gültig) und unterschreibt sie zusammen mit der Aktion und der sortierten Liste der Upload-IDs.
  3. Der Server prüft die Unterschrift gegen den gespeicherten öffentlichen Schlüssel. Passt sie nicht, lehnt er ab. Das gilt für den Papierkorb, für ganze Ordner und für das endgültige Löschen. Wiederherstellen braucht keinen Nachweis.
src/crypto.js · Was unterschrieben wird
export function buildDeleteProofPayload({ userId, challenge, issuedAt, action, uploadIds }) {
    const ids = [...uploadIds].map(String).sort().join(',');
    return ['v1', 'tresor-delete', String(userId), challenge, String(issuedAt), action, ids].join(':');
}

Welche Metadaten sichtbar sind

Zero-Knowledge schützt Inhalte und Schlüssel, nicht automatisch alle Metadaten. Das sollten Sie bei Ihrer Risikoabwägung kennen:

InformationVerschlüsselt?Wo sichtbar?
Dateiinhalt (PDF, Bild, …)JaNur bei Ihnen nach Entschlüsselung
Master-Schlüssel, Passwort, RecoveryN/ANur kurz im RAM nach Entsperren
Dateiname, OrdnernameJa (im Journal)Verschlüsselt im Änderungsjournal auf dem Server; Klartext nur lokal nach Entsperren
Dateigröße, Chunk-Anzahl, ZeitstempelNeinServer (pro Datei, unverschlüsselt)
Tarif, SpeicherbelegungNeinServer (für Abrechnung)
Login-SessionNeinHttpOnly-Cookie Ihres Browsers
Verschlüsselte Blöcke selbstJaObjektspeicher bei Hetzner in Nürnberg. Der Anbieter sieht Größe, Anzahl und Zugriffszeiten der Blöcke, aber keinen Inhalt und keine Dateinamen

Kryptografische Parameter (Referenz)

ParameterWert
Passwort-AbleitungPBKDF2-SHA256, 600.000 Iterationen
VerschlüsselungAES-256-GCM
IV pro Chunk12 Byte, zufällig (Web Crypto)
AAD-BindungfileUuid + Chunk-Index
Chunk-Größe (Klartext)5 MiB
Recovery-PhraseBIP39, 12 Wörter (128 Bit + 4 Bit Prüfsumme)
Reset-NachweisEd25519, Server hält nur den öffentlichen Schlüssel
Einmal-Aufgabe beim Reset32 Byte zufällig, 5 Minuten gültig, einmalig verwendbar
Max. Ordner64 pro Tresor
Session-SchlüsselNicht exportierbar, nur RAM

Warum PBKDF2 und nicht Argon2?

Argon2id ist heute der Goldstandard für Passwort-KDFs. Wir nutzen bewusst PBKDF2-HMAC-SHA256 mit 600.000 Iterationen im Browser:

  • Die native Web-Crypto-API (crypto.subtle) unterstützt kein Argon2id.
  • Eine WASM-Library würde eine zusätzliche Supply-Chain-Angriffsfläche im Zero-Knowledge-Kontext eröffnen.
  • 600.000 Iterationen sind der aktuelle OWASP-Wert für PBKDF2-HMAC-SHA256, das Verfahren selbst ist in NIST SP 800-132 beschrieben.
  • Was das nicht leistet: Argon2id erschwert Angriffe zusätzlich über den Speicherbedarf, PBKDF2 nur über Rechenzeit. Spezialhardware rechnet PBKDF2 daher günstiger durch. Ein langes, ungewöhnliches Passwort gleicht das aus, ein kurzes oder geläufiges nicht.

Server (Überblick)

Hinter /api/tresor/* läuft ein separater Dienst. Er prüft Ihre Datenputzer-Session, verwaltet Tarif und belegten Platz und stellt kurzlebige unterschriebene Adressen aus. Die verschlüsselten Blöcke selbst laufen nicht über ihn, sondern direkt zwischen Ihrem Browser und dem Objektspeicher.

Trennung zwischen Konten: Jede Route prüft, dass die angefragte Datei zu Ihrem Konto gehört, bevor sie eine Adresse ausstellt. Unterschriebene Adressen gelten zwei Minuten und nur für einen Block unter Ihrer eigenen Kontonummer. Der Objektspeicher selbst lässt ohne Unterschrift weder Lesen noch Auflisten zu. Selbst wer eine fremde Upload-ID kennt, bekommt keinen Block: die Anfrage endet mit „nicht gefunden“.

Wer prüfen will, was zwischen Browser und Server läuft, sieht jede Anfrage im Netzwerk-Tab der Entwicklertools, mit Inhalt. Dort lässt sich nachvollziehen, dass weder Passwort noch Schlüssel noch Klartext je an uns gehen.

So können Sie es selbst prüfen

Diese Seite beschreibt, wie der Tresor gebaut ist. Ob Ihr Browser wirklich genau diesen Code ausführt, ist eine eigene Frage, und dafür gibt es eine eigene Seite mit einer Schritt-für-Schritt-Anleitung: Quelltext nachprüfen.

Dort steht unter anderem:

  1. Der Krypto-Worker in voller Länge. Die Datei ist gut ein Kilobyte groß. Sie können sie ganz lesen und selbst nachsehen, dass darin keine einzige Anweisung steht, die etwas ins Netz schicken könnte, und kein einziges Wort über Passwörter.
  2. Haltepunkt im lesbaren Original. Wir liefern Sourcemaps mit, deshalb zeigen die Entwicklertools den echten Quelltext samt Kommentaren statt des gestauchten Codes. Sie können mitten in der Verschlüsselung anhalten und die Werte ansehen.
  3. Der Gegenbeweis. Über die Überschreibungen der Entwicklertools können Sie den Krypto-Code bei sich lokal kaputt machen. Geht der Upload danach nicht mehr, war es der echte Code.
  4. Application → IndexedDB → TresorDatabase: Unter fileBlobs sehen Sie nur verschlüsselte Daten, keine Klartext-Dateien.

Die Prüfsumme der Dateien, die Ihr Browser gerade geladen hat, rechnet er dort selbst aus. Verglichen wird sie gegen unser offenes Logbuch unter /pruefwerte.json, in dem jeder ausgelieferte Stand steht, jeder Eintrag mit dem vorherigen verkettet. Zu den meisten Ständen liegt zusätzlich eine Kopie im Internet Archive. Wir könnten das Logbuch also nicht rückwirkend umschreiben, ohne dass die Kette bricht.

Grenzen und Risiken

Häufige Fragen

Kann der Server mein Passwort aus der Recovery-Phrase ableiten?

Nein. Passwort und Phrase sind getrennte Kanäle zu demselben Master-Schlüssel. Der Server hat nur verschlüsselte Hüllen, keine der beiden Geheimnisse.

Verliere ich meine Dateien, wenn ich nur das Passwort zurücksetze?

Nein, solange Sie die Recovery-Phrase haben und der Master-Schlüssel unverändert bleibt. Es wird nur die Passwort-Hülle erneuert.

Sieht der Server meine Dateinamen?

Nicht im Klartext. Sie stecken verschlüsselt im Journal. Sichtbar bleiben Speicherbelegung, Upload-IDs, Chunk-Anzahlen, Dateigrößen und Zeitstempel (siehe Metadaten).

  • Ihr Gerät: Schadsoftware oder Browser-Erweiterungen könnten beim Entsperren theoretisch auf den Arbeitsspeicher zugreifen. Halten Sie Ihr System und Ihre Extensions sauber.
  • Verlorenes Passwort + verlorene Recovery-Phrase: Ihre Daten sind unwiderruflich weg. Wir können sie nicht zurückholen, weil wir sie nie lesen konnten. Ein Anbieter, der Ihr Passwort zurücksetzen kann, kann auch Ihre Dokumente lesen; beides zusammen geht nicht.
  • Fremder Code auf der Seite: Die Content-Security-Policy erlaubt nur Skripte von dieser Domain und keine Inline-Skripte. Im Krypto-Pfad steckt außerdem keine einzige fremde Bibliothek: crypto.js nutzt nur die eingebaute Web-Crypto des Browsers und die BIP39-Wortliste. Was es nicht gibt, kann auch nicht über eine Lieferkette vergiftet werden. Bliebe der Fall, dass wir manipulierten Code ausliefern, und genau dafür gibt es Quelltext nachprüfen.
  • Kein Teilen: Der Datenputzer Tresor ist ein persönlicher Zero-Knowledge-Speicher für einen Nutzer. Teilen mit anderen ist nicht eingebaut. Der Schlüssel je Datei wäre die Voraussetzung dafür; solange es nicht da ist, verlässt keine Datei Ihren Tresor.
  • Verfügbarkeit ist kein Krypto-Versprechen: Die Verschlüsselung schützt Ihre Inhalte, nicht ihren Fortbestand. Gegen einen Ausfall auf unserer Seite hilft nur eine eigene Kopie. Laden Sie sich das, worauf es Ihnen wirklich ankommt, zusätzlich herunter.

Hinweise oder Anregungen: support@datenputzer.de


Datenputzer Tresor · Öffentliche Client-Dokumentation · Stand Oktober 2026