Zum Inhalt springen

Dateiformat der Archive

Dieses Kapitel beschreibt das interne Speicherformat der Vanderplanki-Archive.

Eine Beispielimplementierung, die Vanderplanki-Archive praktisch ohne Vanderplanki lesen kann, ist der Vanderplanki Reader.

Grundsätzlicher Aufbau

Item Lists, Thumbnails und Dateiinhalte

Auf der obersten Ebene arbeitet Vanderplanki mit Item Lists. Diese werden gemeinsam mit Thumbnails im CAS für Metaobjekte gespeichert. Dateinhalte werden in einem gesonderten CAS für Objekte gespeichert.

Item Lists sind JSON-codierte Objekte, die ein Array mit Items beinhalten. Items können dabei zum Beispiel Ordner oder Dateien sein. Der genaue Aufbau von Item Lists und Items wird weiter unten beschrieben.

Content Addressed Storage (CAS)

Ein CAS speichert beliebig viele Objekte anhand der SHA-256-Hashwerte ihrer Inhalte. Jedes Vanderplanki-Archiv hat sowohl ein CAS für Objekte als auch eines für Metaobjekte. Objekte innerhalb eines CAS können eine beliebige Größe haben.

Chunks

Die Objekte, die in einem der beiden CAS gespeichert sind, werden nicht unmittelbar in den Dateisystemen der Archivspeicher abgelegt, sondern in Form von Chunks. Kleine Objekte werden in einem einzelnen Chunk gespeichert. Große Objekte werden in mehrere Chunks aufgeteilt und es wird ein zusätzlicher Chunk angelegt, welcher eine Liste der einzelnen Fragment-Chunks beinhaltet. Dies wird detaillierter unter Content Addressable Storage beschrieben.

Chunks werden von Vanderplanki ggf. komprimiert, um Speicherplatz zu sparen.

Chunk Storage

Auch Chunks werden nicht unmittelbar in den Dateisystemen der Archivspeicher abgelegt, sondern zu Chunk Packs zusammengefasst. Die maximale Größe der Chunk Packs hängt vom Typ des CAS ab.

Damit Vanderplanki weiß, welcher Chunk sich an welcher Stelle welches Chunk Packs befindet, gibt es eine Chunk Map, welche aus einer oder mehreren Chunk Map Segments besteht.

Chunk Packs und Chunk Map Segments bilden zusammen den Chunk Storage. Dieser wird detaillierter unten auf dieser Seite beschrieben.

Archive Version

Eine Archive Version ist sozusagen der Einstiegspunkt. Die Archive Version beinhaltet unter anderem für das Objekte-CAS als auch für das Metaobjekte-CAS jeweils eine Liste aller zugehörigen Chunk Packs und Chunk Map Segments, sowie der SHA-256-Hash der Stamm-Item List der jeweiligen Archivversion. Der Aufbau der Archive Version ist weiter unten beschrieben.

Blob Files

Sowohl die Archive Version als auch die zugehörigen Chunk Packs und Chunk Map Segments werden als Blob-Dateien in den Archivspeichern gespeichert. Diese tragen als Namen den SHA-256-Hashwert ihrer Inhalte. Dies erleichtert eine Prüfung auf mögliche Fehler erheblich.

Archive Store

In einem Archivspeicher werden abgelegt:

  • Die Blob-Dateien der einzelnen Archive, die auf dem Archivspeicher gespeichert sind
  • Verschlüsselte Datenbanksicherungen
  • Eine Datei store.txt, die im Wesentlichen die UUID des Archivspeichers beinhaltet. Dies erleichtert die Identifikation des Archivspeichers.

Verzeichnisbaum eines Archive Stores

  • Directoryblobs
    • Directoryef094cbe-6169-4acd-ad35-c171dcc343d3
      • Directory2b
        • 2b958309de842686b53db4fdbe5bd388ac426e23589defcf18172e31c9d57b98
      • Directorya2
        • a228effb2c209f473e1ff245c90a30cf6d8e29617c37734ae484e7d900f46e71
        • a237b2a474fa47f687d86ed377e5c4d278dd5a973425a39a4b15bf476e9193cd
        • a29f31a2333af5bad9dcbab03b22b6bb13eb3d2bfcc91cfbeffa1ce8b6c83127
      • Directoryc3
        • c34d8ff3e5361afa32a8763e4c57c6749f6635e2400dca289f7c37d34ac0724b
        • c3a326e7826df18994e3cb44b8838b526bb3a0cee42d5508f11296018531caa2
  • Directorydb-backups
    • Directory2024
      • Directory2024-03
        • encrypted-db-backup-2024-03-10-085256101.txt
        • encrypted-db-backup-2024-03-10-090412106.txt
  • store.txt

Schlüssel ermitteln, die zur Entschlüsselung der Archivversion und der CAS-Inhalte benötigt werden

Datenbank oder Datenbanksicherung lesen

Sowohl Datenbanken als auch Datenbanksicherungen sind JavaScript-Dateien, welche eine Sektion beinhalten, die die Datenbank im JSON-Format enthalten. Die eigentlichen JSON-Daten sind umschlossen von Zeilen mit dem folgenden Inhalt:

// ===== BEGIN JSON =====

und

// ===== END JSON =====

Die eigentlichen JSON-Daten sind umschlossen von einem JSON-Umschlag (“JSON Envelope”). Dessen Nutzdaten sind entweder im Klartext verfügbar (body) oder verschlüsselt. Im letzteren Fall befinden sich die verschlüsselten Nutzdaten BASE64-codiert in der Eigenschaft encryptedBody.

Sind die Nutzdaten verschlüsselt, ist der zur Entschlüsselung zu verwendende Schlüssel zunächst über die Schlüsselableitungsfunktion Scrypt abzuleiten. Die hierfür erforderlichen Parameter salt, keyLength, cost, blockSize und parallelization sind genauso wie der verschlüsselte Schlüssel encryptionKey in keyProtectors -> password bzw. recoveryKey zu finden.

Innerhalb der Nutzdaten befindet sich eine Eigenschaft archives, welche ein JSON-Array beinhaltet, das in der Datenbank verzeichneten Archive auflistet. Die wichtigen Eigenschaften sind hier archiveId, welche die ID des Archivs und damit auch den Namen des Unterverzeichnisses der Archivspeicher angibt, der Name des Archivs name sowie archiveVersionKey und casKey. Letztere beinhalten die Schlüssel, die zur Entschlüsselung der Archivspeicher erforderlich sind.

Im Falle von archiveVersionKey ist dies immer ein hexadezimal codierter String. Der casKey ist ein JSON-Umschlag wie oben beschrieben. Hier ist der Schlüssel verschlüsselt, wenn das Archiv mit einem zusätzlichen Kennwort geschützt wurde. (Der Schutz von Archiven mit einem zusätzlichen Kennwort ist ein Feature, das in der aktuellen Version von Vanderplanki noch nicht generell verfügbar ist.)

Die gewünschte Archivversion kann über das Array versions ermittelt werden (archiveVersionHash). Die Archivversion mit dem höchsten Zeitstempel (created) ist die aktuelle. Beinhaltet der zu entschlüsselnde Archivspeicher nicht die aktuelle Archivversion, kann diese über das Array replicas ermittelt werden.

Datentypen und Datenstrukturen (Englisch)

Data Types

Binary Data Types

bool

0 - false (byte) 1 - true (byte)

byte

One byte.

int32

Little endian-encoded signed 32-bit integer (4 bytes).

int64

Little endian-encoded signed 64-bit integer (8 bytes).

UUID

16 bytes.

Hash

SHA256 hash (32 bytes)

UntypedBlobKey
  • Hash (see above)
  • Length (int32)
Arrays (<datatype>[])
  • Count (int32)
  • Item 1 .. Item n
Key (of encrypted data)
  • Auth key (16 bytes)
  • Cipher key (16 byte)
Encrypted data
  • Initialization vector (IV) (16 bytes)
  • Encrypted data (AES-256-CBC)
  • Message authentication code (HMAC-SHA-256) of IV and encrypted data (32 bytes)
Compressed data

Data is compressed using GZIP.

JSON Data Types

int32

Signed 32-bit integers are written as a JSON number.

int64

Signed 64-bit integers are written as a JSON string because they can potentially exceed JavaScript’s Number.MAX_SAFE_INTEGER (which is 9007199254740991) on the positive side and Number.MIN_SAFE_INTEGER (which is -9007199254740991) on the negative side.

Hash

SHA256 hashes (32 bytes) are written as hexadecimal-encoded strings (0-9, a-f).

Instant

Instants (timestamps) are written as a JSON string in simplified extended ISO format (ISO 8601), which is always 24 characters long (YYYY-MM-DDTHH:mm:ss.sssZ). The timezone is always zero UTC offset (suffix “Z”).

Archive Version

The archive version contents are encrypted with AES-256 as a whole, using the archive version key of the archive.

Archive Version Contents

  • 1d 05 00 - Signature
  • 01 - Version
  • ID of the archive (UUID)
  • Creation time of this archive version (Instant)
  • Parent archive version hash (Hash). If there is no parent archive version, a hash only consisting of zeros is written.
  • Root meta object hash (Hash)
  • Meta objects - Map segment keys (UntypedBlobKey[]), sorted by hash
  • Meta objects - Pack keys (UntypedBlobKey[]), sorted by hash
  • Objects - Map segment keys (UntypedBlobKey[]), sorted by hash
  • Objects - Pack keys (UntypedBlobKey[]), sorted by hash

Chunk Storage

Map

The map consists of one or more map segments, whose contents are encrypted with AES-256 as a whole, using the CAS key of the archive.

Map Segment Contents
  • 1d 05 01 - Signature
  • 01 - Version
  • ChunkInfo[] - sorted by chunk key hash, then chunk key type
  • UntypedBlobKey[] - pack keys used in the chunk infos - sorted
Chunk Info
  • 32 Chunk key hash (hash)
  • 1 Chunk key type (byte)
  • 1 Chunk key subtype (byte)
  • 1 Reserved - always 0 (byte)
  • 1 Reserved - always 0 (byte)
  • 4 Index of pack key (see map segment contents) (int32)
  • 4 Offset within pack (int32)
  • 4 Length within pack (int32)

Packs

Packs consist of

  • A fixed-length header, encrypted with AES-256, always 64 bytes (encrypted length)
  • A variable-length directory, encrypted with AES-256
  • One or more chunks, each encrypted with AES-256

For encryption, the CAS key of the archive is used.

  • 1d 05 02 - Signature
  • 01 - Version
  • Encrypted directory length (int32)
Directory
  • 1d 05 03 - Signature
  • 01 - Version
  • DirectoryEntry[] - sorted by offset
Directory Entry
  • Offset (int32)
  • Length (int32)

Chunk

  • 1d 05 04 - Signature
  • 01 - Version
  • Chunk key hash (hash)
  • Chunk type (byte)
  • Chunk subtype (byte)
  • Is the chunk’s contents compressed? (bool)
  • Does the content’s uncompressed hash equal the chunk key hash? (bool)
  • If it doesn’t:
    • The content’s uncompressed hash (hash)
  • If the contents is compressed:
    • The hash of the compressed contents (hash)
  • Length of compressed (if compressed) or uncompressed contents (int32)
  • The compressed (if compressed) or uncompressed contents

Content Addressable Storage

The content addressable storage (CAS) can store any number of objects with arbitrary length. The CAS internally uses the (encrypted) chunk storage as follows:

Small objects

Small objects are written to the chunk store as a single chunk with

  • Chunk key hash: The hash of the object
  • Chunk type: CAS object (0)
  • Chunk subtype: CAS small object (0)

Large objects

For large objects, a large object descriptor is written (see below) with

  • Chunk key hash: The hash of the object
  • Chunk type: CAS object (0)
  • Chunk subtype: CAS large object fragment list (1)

For each fragment of the large object, a chunk is written

  • Chunk key hash: The hash of the fragment
  • Chunk type: CAS large object fragment (1)
  • Chunk subtype: None (0)

Large object fragment list

  • 1d 05 05 - Signature
  • 01 - Version
  • The hashes of the object’s fragments (hash[])

Item List

Item lists stored in the CAS always begin with {"$":"itemList@ (ASCII). They don’t have a UTF-8 BOM.

{
"$": "itemList@1",
"items": [
{ ... },
{ ... },
{ ... }
]
}

Item

Items can have the following type:

  • fileArchive
  • snapshot
  • folder
  • file

File Archive Item

  • type: fileArchive
  • name: FileArchive
  • timestamp
  • salt
  • parent (optional, hash of the previous root meta object, null = empty root meta object)
  • children

Snapshot Item (named Top-Level Folders in the User Interface)

  • type: snapshot
  • name: snapshot-group-name@instant
  • source (optional)
    • type: local
    • path
  • size (int64)
  • folders (int32)
  • files (int32)
  • children
  • childrenModified (optional, default: false)

Folder Item

  • type: folder
  • name
  • size (int64)
  • folders (int32)
  • files (int32)
  • children

File Item

  • type: file
  • name
  • size
  • created (optional)
  • modified (optional)
  • contents
  • thumbnail (optional)