Backup file format, version 1
Status: draft until app version 1.0 ships. Reference implementation: Packages/KuriosaKit/Sources/KuriosaStore/Backup/BackupFile.swift; a reader in JavaScript, free to use, is website/src/tool/kuriosa-open.js, published on kuriosa.app with the tool "Open a backup without Kuriosa" (export-everything.md, section 9). Reference file: Fixtures/golden/backup-v1/ (decision record 0018).
A backup is one file with a whole library: the catalog, every photo and document, thumbnails, and cut-outs. It stays encrypted with the library's own data key, so it opens with the same keys as the library: the device key on the iPhone that made it, or the recovery key anywhere else.
1. File
- Extension
.kuriosabackup. - All integers are big endian.
| Offset | Length | Content |
|---|---|---|
| 0 | 4 | ASCII KBK1 |
| 4 | 4 | header length H (1 ≤ H ≤ 1 MiB) |
| 8 | H | header, UTF-8 JSON (section 2) |
8 + H | … | entries (section 3), the sealed index last (section 4) |
2. Header
{
"backupID" : "<lowercase uuid>",
"createdAt" : "2026-10-01T12:00:00Z",
"formatVersion" : 1,
"manifest" : { … the library's manifest.json, see library-format.md … }
}
formatVersion: readers refuse versions higher than they support. They also refuse amanifest.formatVersionhigher than they support.createdAt: ISO 8601, UTC, whole seconds.manifest: the library manifest at the time of the backup. Its key slots unlock the data key on a device without the device key.- Readers ignore unknown keys.
The header is plaintext and not authenticated by itself. The sealed index (section 4) repeats backupID and createdAt, and every key slot is bound to the library ID, so a changed header makes reading fail.
3. Entries
Each entry is:
| Length | Content |
|---|---|
| 2 | path length P |
P | path, UTF-8, relative to the package root |
| 8 | data length N |
N | data |
Except for the index, the data is a file of the library package copied byte for byte (already KEF1 sealed, see library-format.md). Allowed paths:
catalog.db.enc(exactly once),media/<id>.enc,media/<id>.<suffix>.encwith a suffix of lowercase letters and hyphens (thumb,cutout,cutout-thumb),
where <id> is a lowercase UUID. Readers refuse any other path, so no entry can be written outside the package. manifest.json is not an entry; it is in the header.
Writers put the catalog first and the media files after it, sorted by path. Readers check the data key with the catalog as soon as it is read (KEF1 context catalog), before copying media files.
4. Sealed index
The last entry has the path backup-index.enc. Its data is a KEF1 sealed file with the library data key and the context backup-index:<backupID>. The plaintext is UTF-8 JSON:
{
"backupID" : "<same as the header>",
"createdAt" : "<same as the header>",
"entries" : [
{ "path" : "catalog.db.enc", "sha256" : "<lowercase hex>", "size" : 204800 },
{ "path" : "media/<id>.enc", "sha256" : "<lowercase hex>", "size" : 1048576 }
]
}
entries lists every entry before the index, in file order, with the SHA-256 of its stored bytes. No bytes may follow the index.
A reader accepts the backup only if the index decrypts, its backupID and createdAt equal the header's, and its entries equal the entries actually read (same paths, order, sizes, and hashes). This detects changed, missing, added, and swapped files, and a file from another backup.
5. Restoring
- Read the header. Get the data key: the device key stored for
manifest.libraryID, or the user's recovery key with the manifest's recovery key slot. - Unpack the entries into a new package folder and verify the index. On any error, delete the folder.
- Write
manifest.jsonfrom the header. If the device's current library has the same library ID, keep its manifest instead: it may hold a newer recovery key. - Open the unpacked package once (decrypt and read the catalog). Only then replace the current library with it.
The app keeps the replaced library on the device until the next restore, so a restore can be undone ("Undo restore" in Settings).
6. What the plaintext reveals
The header and the entry framing reveal the library ID, when the library and the backup were made, the number of media files, and their sizes. They reveal no titles, values, photos, or other collection data. The same is true of a library package on disk.