Skip to content

Boxcryptor format

Krywerk reads and writes the Boxcryptor format so that existing data remains usable after the service was shut down and can be migrated. The description was created using a clean-room approach for the purpose of interoperability; it contains no code of the vendor. Boxcryptor is a trademark of its owner.

Assessment of the methods from today's perspective: Security › Boxcryptor format.

Specification in German

The reconstructed specification below is maintained in German only. Algorithm names, field names, byte layouts and code examples are language independent.

Boxcryptor Verschlüsselungssystem — rekonstruierte Spezifikation

Rekonstruiert aus Boxcryptor für Windows v2.48.1906 (Secomba GmbH) zum Zweck der Interoperabilität / Wiederherstellung eigener verschlüsselter Dateien nach Abschaltung des Boxcryptor-Dienstes. Dies ist eine eigenständige Beschreibung der Algorithmen und des Dateiformats, kein Auszug des proprietären Quellcodes.

Alle Angaben beziehen sich auf das Standard-Format (RawHeader-Id bc01). Der High-Performance-Modus (bc02) wird am Ende gesondert behandelt.


1. Krypto-Primitive

Zweck Algorithmus
Passwort-Schlüsselableitung (KDF) PBKDF2 (RFC 2898) mit HMAC-SHA512
Symmetrische Verschlüsselung AES-256 im CBC-Modus, PKCS7-Padding
Nachrichtenauthentisierung (symmetrisch) HMAC-SHA256 (32 Byte), Encrypt-then-MAC, MAC vorangestellt
Asymmetrisches Key-Wrapping RSA-4096, OAEP mit SHA-1 (.NET fOAEP=true)
RSA-Integrität SHA-256 des Klartexts, vorangestellt (ungeschlüsselt)
Zufall CSPRNG

1.1 Der „doppelte" AES-Schlüssel

Ein AES-Schlüssel ist in diesem System immer doppelt so lang wie die nominale Schlüssellänge. Bei AES-256 sind das 64 Byte, aufgeteilt in:

key[ 0:32]  ->  ForEncryption   (AES-256-Schlüssel)
key[32:64]  ->  ForHashing      (HMAC-SHA256-Schlüssel)

Deshalb liefert die KDF 64 Byte und Zufallsschlüssel sind 64 Byte lang.

1.2 Authentifizierte symmetrische Verschlüsselung

EncryptAuthenticated(plain, iv):
    ct  = AES-256-CBC-PKCS7-Encrypt(ForEncryption, iv, plain)
    mac = HMAC-SHA256(ForHashing, ct)          # 32 Byte
    return mac || ct

DecryptAuthenticated(blob, iv):
    mac = blob[0:32]; ct = blob[32:]
    assert HMAC-SHA256(ForHashing, ct) == mac  # sonst InvalidMac
    return PKCS7-Strip(AES-256-CBC-Decrypt(ForEncryption, iv, ct))

Der IV wird separat übergeben (nicht Teil von blob), außer wo unten anders vermerkt.

1.3 Authentifiziertes RSA-Wrapping

RSA-Wrap(plain, pubkey):
    inner = SHA-256(plain) || plain          # 32 Byte Hash vorangestellt
    return RSA-OAEP-SHA1-Encrypt(pubkey, inner)   # ggf. in 440-Byte-Chunks

RSA-Unwrap(cipher, privkey):
    inner = RSA-OAEP-SHA1-Decrypt(privkey, cipher)  # 512-Byte-Blöcke
    hash = inner[0:32]; plain = inner[32:]
    assert SHA-256(plain) == hash
    return plain

RSA-Chunking: Klartext-Chunk max. 440 Byte → 512-Byte-Chiffrat je Block. Beim Entschlüsseln werden 512-Byte-Blöcke gelesen. Ein File-Key (96 Byte innen) passt in einen Block.


2. Schlüsselhierarchie

Passwort (UTF-8)
   │  PBKDF2-HMAC-SHA512(salt, iterations) → 64 Byte
   ▼
Account-AES-Schlüssel  (enc=32 ‖ mac=32)
   │  DecryptAuthenticated(EncryptedRsaPrivateKey)
   ▼
RSA-4096-Privatschlüssel
   │  RSA-Unwrap(encryptedFileKeys[i].value)
   ▼
Datei-AES-Schlüssel  (enc=32 ‖ mac=32)   ← pro Datei einzigartig
   │  AES-256-CBC pro 4096-Byte-Block
   ▼
Klartext-Dateiinhalt

2.1 Account-Schlüssel aus Passwort

account_key = PBKDF2(hash=SHA512, pw=utf8(password), salt=salt,
                     iterations=kdfIterations, dklen=64)

salt und kdfIterations stammen aus dem lokalen Benutzerkonto (siehe §5), nicht aus der .bc-Datei.

2.2 Verschlüsselter RSA-Privatschlüssel (EncryptedRsaPrivateKey.Value)

Base64-kodierter Blob:

value_bytes = base64decode(Value)
iv   = value_bytes[0:16]
blob = value_bytes[16:]
pem_body_ascii = DecryptAuthenticated(blob, iv)   # mit account_key
der = base64decode(pem_body_ascii)                # PKCS#1 RSAPrivateKey (DER)

Der entschlüsselte Klartext ist der ASCII-Base64-Körper eines PKCS#1-RSA PRIVATE KEY (ohne -----BEGIN/END----- und ohne Zeilenumbrüche). Nach erneutem Base64-Dekodieren erhält man die DER-Bytes des privaten Schlüssels.

2.3 Datei-Schlüssel aus dem Header

Jeder Eintrag in encryptedFileKeys ist für einen KeyHolder (Benutzer oder Gruppe) RSA-gewrappt:

file_key = RSA-Unwrap( base64decode(entry.value), rsa_private_key )   # 64 Byte

3. .bc-Dateiformat

┌───────────────────────────── Offset 0
│ RawHeader               48 Byte (binär, fest)
├───────────────────────────── Offset 48
│ Core-JSON               HeaderCoreLength Byte (UTF-8)
├───────────────────────────── Offset 48 + HeaderCoreLength
│ Null-Padding            HeaderPaddingLength Byte (0x00)
├───────────────────────────── Offset = GetLength()  (≥ 4096, Vielfaches von 4096)
│ Verschlüsselter Inhalt  blockweise (siehe §4)
└─────────────────────────────

GetLength() = 48 + HeaderCoreLength + HeaderPaddingLength. Der Header wird auf mindestens 4096 Byte (bzw. ~1 % der Dateigröße, gedeckelt bei 131072) aufgefüllt.

3.1 RawHeader (48 Byte, Little-Endian)

Offset Länge Feld Beschreibung
0 4 Id ASCII "bc01" (Standard) oder "bc02" (High-Performance)
4 4 HeaderCoreLength uint32 LE — Länge des Core-JSON
8 4 HeaderPaddingLength uint32 LE — Länge des Null-Paddings
12 4 CipherPaddingLength uint32 LE — PKCS7-Padding-Bytes des letzten Inhaltsblocks
16 32 HeaderCoreMac HMAC-SHA256(file_key.mac, Core-JSON-Bytes)

3.2 Core-JSON

{
  "artifact": "<string>",
  "cipher": {
    "algorithm": "AES",
    "keySize": 256,
    "blockSize": 4096,
    "mode": "CBC",
    "padding": "PKCS7",
    "mac": { "enabled": false, "bytes": 0, "step": 0, "useDataKey": false },
    "iv": "<base64, 16 Byte>"
  },
  "metadata": {
    "name": { "encrypted": <bool>, "value": "<string>", "mac": "<string>" }
  },
  "version": 1,
  "encryptedFileKeys": [
    { "keyHolderId": "<id>", "type": "<string>", "value": "<base64 RSA-Block>" }
  ]
}
  • cipher.iv — 16-Byte-Basis-IV der Datei (Base64).
  • cipher.mac.enabled — optionaler In-Stream-MAC pro Block; standardmäßig aus. Ist er an, trägt jeder Block zusätzliche bytes MAC-Bytes im Abstand step (Schlüssel: useDataKey entscheidet zwischen Datei-MAC-Key und separatem Key). Im Normalfall irrelevant.
  • encryptedFileKeys — eine oder mehrere RSA-gewrappte Kopien des Datei-Schlüssels, je KeyHolder.

4. Inhaltsverschlüsselung (blockweise AES-256-CBC)

Der Inhalt ist in Blöcke der Größe cipher.blockSize (4096 Byte Klartext) aufgeteilt, die unabhängig verschlüsselt werden (ermöglicht wahlfreien Zugriff). Jeder Block erhält einen eigenen IV, abgeleitet aus Basis-IV + Blockindex + Datei-Enc-Key:

IV_i = HMAC-SHA256( key = file_key.ForEncryption,
                    msg = base_iv(16) ‖ uint64_le(i) )[0:16]

(i = 0-basierter Blockindex, als 8 Byte little-endian angehängt.)

4.1 Entschlüsselung

offset        = GetLength()
content       = datei[offset:]
blockSize     = 4096
cipherPad     = RawHeader.CipherPaddingLength

Zerlege content in Blöcke: alle außer dem letzten sind 4096 Byte Chiffrat,
der letzte ist der Rest (content_len - 4096*(n-1)).

für jeden Block i:
    IV_i  = SetIVec(base_iv, i, file_key.ForEncryption)
    plain = AES-256-CBC-Decrypt(file_key.ForEncryption, IV_i, ct_i)   # ohne Padding
    wenn i == letzter Block und content nicht Vielfaches von 4096:
        plain = PKCS7-Strip(plain)        # entfernt cipherPad Bytes
Klartext = Konkatenation aller plain

Padding-Regel (aus SetFileLength): CipherPaddingLength = 16 − (gesamtlänge mod 16), gesetzt genau dann, wenn die Klartext-Gesamtlänge kein Vielfaches von 4096 ist (d. h. ein Teilblock existiert). Volle Dateien (Vielfaches von 4096) haben cipherPad = 0 und keinen PKCS7-Trailer.

4.2 Verschlüsselung (Umkehrung)

base_iv = Random(16);  file_key = Random(64)
für jeden 4096-Byte-Klartextblock i:
    letzter Teilblock? -> PKCS7 bis Vielfaches von 16, CipherPaddingLength merken; sonst Padding=None
    IV_i = SetIVec(base_iv, i, file_key.ForEncryption)
    ct_i = AES-256-CBC-Encrypt(file_key.ForEncryption, IV_i, plain_i)
Header aufbauen, encryptedFileKeys = RSA-Wrap(file_key) je KeyHolder,
HeaderCoreMac = HMAC-SHA256(file_key.ForHashing, core_json)

5. Herkunft von Salt / Iterationen / Privatschlüssel

Die .bc-Datei enthält nur die RSA-gewrappten Datei-Schlüssel. Zum Entsperren braucht man das Benutzerkonto:

  • Lokale Datenbank: %LOCALAPPDATA%\Boxcryptor (SQLite, via SQLite.Net). Enthält KeyHolder mit EncryptedRsaPrivateKey, zugehörigem salt und kdfIterations (Felder im Account-/KeyServer-JSON, Namespace Boxcryptor.Core.KeyServer.Json).
  • Alternativ aus einem Boxcryptor-Account-Export / Key-Server-Antwort (JSON mit denselben Feldern).

Weitere abgeleitete Werte (nur informativ, nicht für die Datei-Entschlüsselung nötig):

  • Server-Passwort-Hash: base64( PBKDF2-HMAC-SHA512( pw, <konstante>+email, 5000, 48 ) )
  • Protection-Hash: base64( PBKDF2-HMAC-SHA512( pw, salt, iter, 64 ) )
  • Protection-Salt: utf8(<konstante>) ‖ Random(24)

6. Dateinamen-Verschlüsselung

Verschlüsselte Namen sind in einer Base-4K-Textkodierung (§6.4) abgelegt. Dekodierter Byte-Aufbau:

Offset Länge Feld
0 1 Tag = 0x01
1 1 KeyHolder-ID-Prüfbyte = fold8(utf8(keyHolder.Id)) (§6.3)
2 5 MAC = erste 5 Byte von HMAC-SHA256(keyHolder.aesKey.ForHashing, klartext)
7 … Chiffrat, genauso lang wie der UTF-8-Klartext (CFB ohne Padding)

Erkennung eines verschlüsselten Namens: Base-4K-dekodierbar, dekodierte Länge > 7, Byte 0 = 0x01.

6.1 Schlüssel

Der KeyHolder nutzt hier seinen EncryptedAesKey (Nutzer: encryptedAesKey des Kontos; Gruppe: AES-Key der Gruppe), nicht den Datei-Key. Welcher KeyHolder das ist, bestimmt der Header der Datei (bei genau einer id dieser KeyHolder, bei mehreren die AdHoc-Gruppe über alle ids, siehe Teamfunktionen).

Beim Entschlüsseln kennt der Client den KeyHolder nicht; er filtert alle eigenen KeyHolder (Nutzer + rekursiv Gruppen) nach dem Prüfbyte an Offset 1 und probiert deren AES-Keys der Reihe nach. Ein Kandidat mit passendem Prüfbyte, aber falschem MAC wird übersprungen; erst wenn der letzte Kandidat scheitert, schlägt die Entschlüsselung fehl.

6.2 Chiffre

Zwei-Pass-Konstruktion mit AES-CFB, 128-Bit-Feedback, kein Padding (BouncyCastle CfbBlockCipher; ein unvollständiger letzter Block wird als Stromchiffre verarbeitet, daher beliebige Längen) und Byte-Propagation (eine IGE-ähnliche „Lawine“), damit der gesamte Name von jedem Byte abhängt:

base = HMAC-SHA256(aesKey.ForHashing, mac5)[0:16]
n    = fold64(base)                         # §6.3 – Achtung: letztes Byte wird NICHT einbezogen

IV(k) = HMAC-SHA256(aesKey.ForEncryption, base ‖ uint64_le(k))[0:16]     # = SetIVec aus §4

Entschlüsseln(ct):
    x = CFB-Decrypt(aesKey.ForEncryption, IV(n+1), ct)
    x = unpropagate_backward(x)             # für i = len-1 … 1: x[i] ^= x[i-1]
    x = reverse_in_64byte_chunks(x)         # jeden 64-Byte-Block (Rest kürzer) in sich umkehren
    x = CFB-Decrypt(aesKey.ForEncryption, IV(n), x)
    x = unpropagate_backward(x)
    return utf8(x)

Verschlüsseln(plain):
    x = propagate_forward(plain)            # für i = 0 … len-2: x[i+1] ^= x[i]
    x = CFB-Encrypt(aesKey.ForEncryption, IV(n), x)
    x = reverse_in_64byte_chunks(x)
    x = propagate_forward(x)
    return CFB-Encrypt(aesKey.ForEncryption, IV(n+1), x)

Prüfung nach dem Entschlüsseln: erste 5 Byte von HMAC-SHA256(ForHashing, klartext) müssen mac5 entsprechen (Vergleich in konstanter Zeit). Der Client bereinigt den Klartext anschließend für Windows: ungültige Dateinamenzeichen → _, abschließende Leerzeichen und Punkte werden entfernt.

6.3 XOR-Faltung (fold)

fold64(b):                                  # byte[] → uint64
    acc = [0]*8
    for i in 0 … len(b)-2:                  # letztes Byte wird ausgelassen (Off-by-one im Original)
        acc[i % 8] ^= b[i]
    return big_endian_uint64(acc)           # acc[0] ist das höchstwertige Byte

fold32(v) = (v >> 32) ^ (v & 0xFFFFFFFF)
fold16(v) = (v >> 16) ^ (v & 0xFFFF)
fold8(b)  = fold8_(fold16(fold32(fold64(b))))  mit fold8_(v) = (v >> 8) ^ (v & 0xFF)

n in §6.2 ist fold64(base) (Bytes 0–14 der 16-Byte-base). Das Prüfbyte an Offset 1 ist fold8(utf8(keyHolder.Id)).

6.4 Base-4K-Kodierung

Keine Alphabet-Tabelle, sondern Arithmetik: Der Bytestrom wird in 12-Bit-Gruppen (3 Nibbles) zerlegt; jede Gruppe ergibt den Codepoint 0x6000 + wert (U+6000…U+6FFF, CJK-Bereich), UTF-8-kodiert (3 Byte pro Zeichen). Bleiben am Ende Nibbles übrig, folgt ein Abschlusszeichen aus U+4000…U+40FF:

Restnibbles (2·len mod 3) Abschlusszeichen
0 keines
2 0x4000 + letztes_byte
1 0x4000 + (letztes_byte & 0xF)

Dekodierung: Codepoints parsen; jeder muss in U+6000…U+6FFF liegen, nur der letzte darf stattdessen in U+4000…U+40FF liegen. Je zwei 12-Bit-Werte ergeben 3 Byte (hi8(v0), lo4(v0)‖hi4(v1), lo8(v1)); ein Abschlusszeichen liefert bei gerader Position ein ganzes Byte, bei ungerader lo4(v_prev)‖lo4(abschluss). Legacy: Der Decoder akzeptiert alternativ die Basis 0x5000 (U+5000…U+5FFF); der Encoder dieser Version schreibt nur 0x6000. Ein Decoder sollte beide Bereiche annehmen.


7. High-Performance-Modus (bc02)

Identische Krypto (gleicher Header, gleicher FileKey/IV-Mechanismus, gleiche Blockverschlüsselung). Der Modus dient nicht schnelleren Schreibzugriffen, sondern einer schnelleren Verzeichnisauflistung: Die Klartextgröße einer Datei soll aus der physischen Dateigröße berechenbar sein, ohne den Header zu lesen. Dafür gelten zwei Zusatzregeln:

7.1 Feste Header-Länge

Der Header ist bei bc02 immer auf ein Vielfaches von 4096 Byte aufgefüllt (praktisch: genau 4096), unabhängig von der Dateigröße. Bei bc01 wächst die Mindestlänge mit der Datei: < 512 KiB → 4096; 512 KiB … < 10 MiB → ceil(len · 0,01 / 4096) · 4096; ≥ 10 MiB → 131072. Da auch diese Werte Vielfache von 4096 sind, prüft der Client im HP-Modus beim Öffnen nur, dass GetLength() ein Vielfaches von 4096 ist, und verwirft sonst die Datei (InvalidHeaderLengthException).

7.2 Overlapping Bytes

Hat die Datei einen Teilblock (Klartextlänge kein Vielfaches von 4096), wird der letzte Block PKCS7-gepolstert (pad = 16 − len mod 16, Bereich 1–16, als CipherPaddingLength im RawHeader). Bei bc02 werden hinter das Chiffrat des letzten Blocks zusätzlich ovl Bytes geschrieben:

ovl = 0        wenn pad == 16   (len mod 16 == 0)
ovl = pad      sonst            (Bereich 1–15)

Jedes dieser Bytes hat den Wert ovl (sieht aus wie PKCS7-Padding) und steht unverschlüsselt und unauthentisiert in der Datei. Sie werden beim Lesen nie angefasst; die Klartextlänge ergibt sich aus

plain_len = S − GetLength() − pad − ovl          (S = physische Dateigröße)

Zweck: Damit gilt S mod 16 == pad (bei bc01 ist S mod 16 immer 0 und sagt nichts aus). Der Client schätzt im HP-Modus die Klartextgröße ohne Header-Lesen als S − 4096 − 2·(S mod 16), sofern S mod 16 ≠ 0 (S == 4096 → leere Datei). Ist S mod 16 == 0, ist der Fall mehrdeutig (pad == 16 oder kein Teilblock), und der Header wird doch gelesen.

7.3 Entschlüsseln

Wie bc01 (§4.1): Blöcke zu 4096 Byte Chiffrat, letzter Block rest + pad Byte mit PKCS7-Strip. Einziger Unterschied: Die letzten ovl Bytes der Datei gehören nicht zum Chiffrat und sind zu ignorieren. Der Wert lässt sich aus dem Header ableiten (ovl = CipherPaddingLength == 16 ? 0 : CipherPaddingLength); auf das Lesen der Bytes selbst sollte man sich nicht verlassen, da sie ungeschützt sind.

7.4 Weitere Eigenschaften

  • Formatwahl: Der Id-Wert wird beim Anlegen eines Headers ohne Vorlage aus der Einstellung IsHighPerformanceModeEnabled bestimmt. Neue Dateien/Ordner in einem verschlüsselten Ordner übernehmen dagegen die Id des übergeordneten FolderKey.bch – in einem bestehenden bc01-Baum entstehen also weiterhin bc01-Dateien, auch bei aktivem HP-Modus. Beide Formate sind gemischt lesbar.
  • Genau ein KeyHolder: Ein bc02-Header mit mehr als einem encryptedFileKeys-Eintrag wird beim Anlegen verworfen (InvalidDataException).
  • Keine Dateinamen-Verschlüsselung: Im HP-Modus gibt der Client Dateinamen unverändert zurück, die Policy FilenameEncryptionForbidden gilt implizit; die Policy FilenameEncryptionRequired schaltet den HP-Modus beim Login ab.
  • Die Einstellung didVerifyFileFormatForHighPerformanceMode merkt sich, ob der Bestand beim Aktivieren einmal auf passende Header-Längen geprüft wurde.

Teamfunktionen (Client 2.48, dekompiliert am 2026-10-12; Beschreibung, kein Code)

Viele String-Literale im Client sind verschleiert; belegt sind Klassen-/Feldnamen, JSON-Keys und Verhalten.

  • Ein Header-Eintrag pro Datei – beim Schreiben. Dieser Client schreibt in encryptedFileKeys immer genau einen KeyHolder (id, type, value; type in Echtdaten: data). Beim Lesen werden jedoch alle Einträge der Reihe nach probiert und der erste genommen, den der Nutzer entsperren kann; die Beschränkung auf einen Eintrag wird nur bei bc02 erzwungen. Header mit mehreren Einträgen (ältere Clients, andere Plattformen) sind also gültig – ein Recovery-Tool muss alle durchprobieren. Ob eine id ein Nutzer oder eine Gruppe ist, steht nicht im Header – der Client sucht die id in den eigenen KeyHoldern (Nutzer + rekursiv alle Gruppen).
  • type wird beim Entschlüsseln ignoriert. Bei der Vererbung vom Elternordner (neuer FileKey aus Vorlagen-Header) wird aber geprüft, dass alle Einträge der Vorlage den Typ data haben; sonst wird die Vorlage als ungültig verworfen.
  • Mehrere Berechtigte = AdHoc-Gruppe. Teilen mit mehreren Personen legt serverseitig eine AdHoc-Gruppe an (hash = base64(SHA256(sortierte, konkatenierte KeyHolder-IDs))), wird per Hash wiederverwendet; alle Teilnehmer sind MEMBER (bei regulären Gruppen ist der Ersteller OWNER). Diese Gruppe ist der einzige Header-Eintrag. Hinweis zur Sortierung: Der Client sortiert mit dem .NET-Standardvergleich (kulturabhängig, nicht ordinal). Bei den üblichen hexadezimalen IDs ergibt eine ordinale Sortierung dasselbe Ergebnis.
  • Dateinamen-KeyHolder bei Mehrfacheinträgen: Enthält ein Header mehr als eine id, ist der KeyHolder für die Dateinamen-Verschlüsselung keiner der Einträge, sondern die AdHoc-Gruppe, deren Hash sich aus allen ids ergibt. Bei genau einer id ist es dieser KeyHolder.
  • Gruppenschlüssel: Jede Gruppe hat ein RSA-Paar, dessen privater Teil mit einem zufälligen AES-„membershipKey“ verschlüsselt ist. Pro Mitglied liegt der membershipKey RSA-gewrappt (Public Key des Mitglieds, JSON encryptedMembershipKey) und AES-gewrappt (JSON aesEncryptedMembershipKey, Schlüssel: der Membership-AES-Key des Mitglieds, intern GROUP_MEMBERSHIP; der JSON-Name dieses Keys ist im Client verschleiert, keys.groupMembership stammt aus Echtdaten) in der Membership. Beim Entsperren wird zuerst der AES-Pfad versucht, RSA ist der Fallback. Gruppen können verschachtelt sein (auch Gruppen besitzen einen Membership-AES-Key). Gruppenarten: Regular, AdHoc, MsTeams.
  • Vererbung: Neue Dateien/Ordner übernehmen den KeyHolder des übergeordneten FolderKey.bch (neuer FileKey, gleiche Dateinamen-Einstellung). Rechteänderung wrappt den FileKey nur neu; verschlüsselte Namen werden dabei umbenannt (Namensschlüssel hängt am KeyHolder).
  • Master Key (Firma): steht nicht in Dateiheadern. Er ist ein Schlüssel-Escrow: Der Nutzer hinterlegt seinen passwortabgeleiteten Schlüssel RSA-verschlüsselt mit dem Organisations-Public-Key (organizationKey); der Admin kann damit den privaten Nutzerschlüssel entsperren (= Passwort-Reset). Admin-Seite: Ist für den Admin eine Master-Key-Organisation aktiv, erweitert der Client dessen KeyHolder-Liste um die KeyHolder aller Organisationsnutzer. Der Admin kann so jede Datei der Organisation öffnen, ohne dass sie je für ihn verschlüsselt wurde – deshalb taucht er nie im Header auf.
  • Policies (13, vollständig): EncryptionRequired, FilenameEncryptionForbidden, FilenameEncryptionRequired, MasterKey, MasterKeyUnlockable, BusinessUseForbidden, PasswordLengthMin, PasswordRememberForbidden, PermissionsModifyForbidden, RootfoldersMax, LocationsRequired, LocationsWhitelist, ShareSecurePublicLinkDisabled (Whisply). Kamen vom Server.
  • .bckey-Format: artifact, version, users, groups, groupMemberships, organization, license. Lokale Konten: genau ein Nutzer, Teilen mit Fremden praktisch nicht möglich. Der vorliegende Export enthält 1 Nutzer, 0 Gruppen, 0 Memberships.
  • Gruppenverwaltung, Whisply, Audit, SSO: serverseitig/Web – ohne Server nicht nachbaubar.
  • Nachfolge in Krywerk: Teamfunktionen werden nicht im Boxcryptor-Format nachgebaut, sondern im modernen Modus über einen selbst gehosteten Cryptomator Hub (Berechtigung pro Tresor statt pro Datei; Gruppen/SSO über Keycloak; Admin-Zugriff als sichtbares Mitglied statt verdecktem Master Key). Planung und Abbildung der Funktionen: HANDOFF.md 7.9 (Phase F).

8. Sicherheitsbewertung (Stand 2026-10)

Einschätzung der gewählten Verfahren gegenüber dem heutigen Stand der Technik. Grundlage: Client 2.48.1906, Standardkonfiguration. Für die Wiederherstellung ist dieser Abschnitt ohne Belang – das Format muss so nachgebaut werden, wie es ist. Er begründet, warum neue Daten nicht mehr in diesem Format abgelegt werden sollten.

8.1 Bewertung je Baustein

Baustein Umsetzung Bewertung
Passwort-KDF PBKDF2-HMAC-SHA512, 10 000 Iterationen (Konto), 5 000 (lokales Schutzpasswort) ❌ Veraltet. OWASP empfiehlt für PBKDF2-SHA512 ≈ 210 000 Iterationen; Stand der Technik ist Argon2id oder scrypt. Offline-Angriffe auf den .bckey-Export sind bei schwachem Passwort realistisch.
Server-Passwort-Hash PBKDF2, 5 000 Iterationen, Salt = Konstante ‖ E-Mail ❌ Vorhersagbares Salt erlaubt Vorberechnung. (Nur für den abgeschalteten Server relevant.)
Symmetrische Verschlüsselung AES-256-CBC + HMAC-SHA256, Encrypt-then-MAC, getrennte Schlüsselhälften ✅ Korrekt aufgebaut. Heute würde man AES-GCM oder ChaCha20-Poly1305 wählen; CBC+EtM ist aber nicht schwächer.
Dateiinhalt AES-CBC je 4096-Byte-Block; In-Stream-MAC standardmäßig aus ❌ Größte Schwachstelle, siehe 8.2.
Key-Wrapping RSA-4096, OAEP mit SHA-1 ⚠️ Noch sicher (OAEP benötigt keine Kollisionsresistenz), aber nicht quantenresistent („harvest now, decrypt later“) – das gilt allerdings 2026 für praktisch alle Produkte dieser Generation.
RSA-„Integrität“ SHA-256 des Klartexts, unverschlüsselt vorangestellt ⚠️ Erkennt nur Übertragungsfehler, ist keine Signatur: beweist nicht, wer den Dateischlüssel erzeugt hat.
Dateinamen Eigenkonstruktion (2× AES-CFB + Byte-Propagation), MAC 5 Byte (40 Bit), deterministisch ⚠️ Kein Standardverfahren (heute: AES-SIV). Determinismus ist bei Namensverschlüsselung aber üblich und meist gewollt; 40-Bit-MAC ist knapp, für Dateinamen jedoch ein vertretbarer Kompromiss.
MAC-Vergleich ConstantTimeCompare (XOR-Akkumulation ohne frühen Abbruch) für Header- und Namens-MAC ✅ Korrekt.
Zufall RNGCryptoServiceProvider (Windows-CSPRNG) ✅ Korrekt.

8.2 Hauptschwachstelle: Dateiinhalt ist nicht authentisiert

HeaderCoreMac wird ausschließlich über die Core-JSON-Bytes gebildet. Nicht abgedeckt sind:

  • die ersten 16 Byte des RawHeaders (Id, HeaderCoreLength, HeaderPaddingLength, CipherPaddingLength),
  • der gesamte verschlüsselte Inhalt.

Ein Angreifer mit Schreibzugriff auf den Cloud-Speicher kann daher ohne Schlüssel und unbemerkt

  • Bits kippen (CBC-Bitflipping: ein Block wird zu Müll, die ersten 16 Byte des Folgeblocks sind gezielt steuerbar),
  • die Datei abschneiden,
  • einzelne 4096-Byte-Blöcke durch ältere Versionen derselben Datei ersetzen (Block-IVs hängen nur von Basis-IV und Blockindex ab).

Der Client prüft beim Entschlüsseln nur das PKCS7-Padding. Vertraulichkeit bleibt gewahrt, Integrität nicht.

Beide Integritätsprüfungen sind zudem über die Client-Konfiguration abschaltbar (VerifyHeaderMac, DisableFilenameEncryptionIntegrity; Standard: Prüfung aktiv). Bei deaktivierter Namensprüfung wird ein Name mit falschem MAC akzeptiert, sofern das Ergebnis gültiges UTF-8 ist.

IV-Wiederverwendung bei In-Place-Änderungen (bestätigt). FileKey und Basis-IV werden nur beim Anlegen einer Datei erzeugt (FileMode.Create/CreateNew); wird eine bestehende Datei geöffnet, gekürzt oder überschrieben, bleibt der vorhandene Header mit Key und IV erhalten. Da IV_i nur aus Basis-IV und Blockindex abgeleitet wird, gilt für alle Versionen derselben Datei – in bc01 wie bc02:

  • Unveränderte 4096-Byte-Blöcke liefern byteidentisches Chiffrat; ein geänderter Block ändert sich ab der ersten geänderten 16-Byte-Position bis zum Blockende. Wer mehrere Cloud-Versionen vergleicht, sieht also auf 16 Byte genau, wo editiert wurde.
  • Gleicher Klartext an gleicher Blockposition ergibt in jeder Version dasselbe Chiffrat (deterministisch).
  • Ein Schreiben identischen Inhalts (Truncate + Neuschreiben) verändert das Chiffrat gar nicht.

Vertraulichkeit des Inhalts selbst ist dadurch nicht gebrochen (CBC mit festem IV offenbart nur Gleichheit von Blöcken an gleicher Position), aber es ist ein Informationsleck, das eine authentisierte Verschlüsselung mit frischen Nonces (z. B. Cryptomator) nicht hat. Der scheinbare Vorteil beim Delta-Sync ist deshalb eine Schwäche, kein Feature; praktisch ist er ohnehin bedeutungslos, da Dropbox in 4-MB-Blöcken synchronisiert und Anwendungen, die Dateien neu schreiben (Temp-Datei + Rename, CREATE_ALWAYS), in jedem System vollständig neues Chiffrat erzeugen.

8.3 Konsequenzen

  • Recovery: keine. Das Tool muss das Format exakt so verarbeiten.
  • Konto-Export (.bckey) gut schützen: Wegen der niedrigen Iterationszahl ist er bei schwachem Passwort offline angreifbar. Im eigenen Export prüfen, ob kdfIterations tatsächlich 10 000 ist.
  • Keine neuen Daten in diesem Format. Aktuelle Alternativen: Cryptomator (scrypt, AES-GCM, AES-SIV für Namen), rclone crypt (scrypt, XSalsa20-Poly1305), age für Einzeldateien (X25519, optional Post-Quanten-Hybrid).