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ätzlichebytesMAC-Bytes im Abstandstep(Schlüssel:useDataKeyentscheidet 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, viaSQLite.Net). Enthält KeyHolder mitEncryptedRsaPrivateKey, zugehörigemsaltundkdfIterations(Felder im Account-/KeyServer-JSON, NamespaceBoxcryptor.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
IsHighPerformanceModeEnabledbestimmt. Neue Dateien/Ordner in einem verschlüsselten Ordner übernehmen dagegen die Id des übergeordnetenFolderKey.bch– in einem bestehendenbc01-Baum entstehen also weiterhinbc01-Dateien, auch bei aktivem HP-Modus. Beide Formate sind gemischt lesbar. - Genau ein KeyHolder: Ein
bc02-Header mit mehr als einemencryptedFileKeys-Eintrag wird beim Anlegen verworfen (InvalidDataException). - Keine Dateinamen-Verschlüsselung: Im HP-Modus gibt der Client Dateinamen unverändert zurück, die Policy
FilenameEncryptionForbiddengilt implizit; die PolicyFilenameEncryptionRequiredschaltet den HP-Modus beim Login ab. - Die Einstellung
didVerifyFileFormatForHighPerformanceModemerkt 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
encryptedFileKeysimmer genau einen KeyHolder (id,type,value;typein 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 beibc02erzwungen. Header mit mehreren Einträgen (ältere Clients, andere Plattformen) sind also gültig – ein Recovery-Tool muss alle durchprobieren. Ob eineidein Nutzer oder eine Gruppe ist, steht nicht im Header – der Client sucht dieidin den eigenen KeyHoldern (Nutzer + rekursiv alle Gruppen). typewird 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 Typdatahaben; 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 allenids ergibt. Bei genau eineridist 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 (JSONaesEncryptedMembershipKey, Schlüssel: der Membership-AES-Key des Mitglieds, internGROUP_MEMBERSHIP; der JSON-Name dieses Keys ist im Client verschleiert,keys.groupMembershipstammt 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, obkdfIterationstatsä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).