# HarborKeep encrypted backup V1 interoperability

The current app writes `.harborkeepbackup`; older files can use `.mskexport`. Both use the JSON format described here. Extensions are not a cryptographic input.

## Envelope and cryptography

The outer UTF-8 JSON object contains:

| Field | Meaning |
| --- | --- |
| `format` | Exact string `my-secret-keeper-export` |
| `version` | Integer `1` |
| `createdAt` | ISO 8601 timestamp; informational |
| `kdf` | Exact string `PBKDF2-HMAC-SHA256` |
| `iterations` | PBKDF2 round count; current writer uses `200000` |
| `saltB64` | Standard Base64 salt; current writer generates 16 random bytes |
| `nonceB64` | Standard Base64 encoding of the 12-byte GCM nonce |
| `sealedBoxCombinedB64` | Standard Base64 of `nonce || ciphertext || authentication tag` |
| `counts` | Optional informational counts; never authoritative for recovery |

Use the passphrase's exact UTF-8 representation, without normalization or trimming. Derive **32 bytes** with PBKDF2-HMAC-SHA256 using the stored salt and iteration count. Decrypt with **AES-256-GCM**, the 12-byte nonce, a **16-byte/128-bit authentication tag**, and **no associated authenticated data**. The combined value's first 12 bytes must match `nonceB64`; its final 16 bytes are the tag. Authenticate before using plaintext.

The app's V1 reader accepts iteration counts from 1 through 1,000,000, salts of at least 8 bytes, 12-byte nonces, and combined values of at least 28 bytes. Wrong passphrases and damaged ciphertext can both cause authentication failure. The envelope's `createdAt` and `counts` are not authenticated by GCM; use the decrypted payload for record counts and content. The ciphertext is not a ZIP or compressed stream: its plaintext is UTF-8 JSON.

No Apple Account, iCloud connection, device passcode, main-vault key, or HarborKeep installation is needed to decrypt a saved backup with its passphrase. The main-vault key is used by the app before backup creation to read encrypted database content; it is not the backup encryption key.

## Payload schema 1

The decrypted JSON contains `exportedAt`, optional `appVersion`, integer `schemaVersion: 1`, `categories`, and `secrets`. Dates use ISO 8601. UUID strings identify categories, records, attachments, and relationships. Optional fields can be missing in older files; unknown optional fields should be preserved where practical. Unsupported versions must be rejected rather than interpreted as an old schema.

Categories include `id`, `name`, optional `icon`, `iconColor`, `customEmoji`, `customImageDataB64`, `priority`, and `fieldOverrides`. Overrides contain `id`, `fieldName`, `descriptionText`, `placeholder`, and optional `isSensitive`. Field keys map as follows:

| Stored record key | Category override key |
| --- | --- |
| `userId` | `Account` |
| `value` | `Credentials` |
| `url` | `WebSite` |
| `field1` | `Additional1` |
| `field2` | `Additional2` |

Records contain `id`, optional `categoryId`/`categoryName`, `title`, those five field values, `notes`, `importantDateValue`, `importantDateType`, `showsImportantDateOnDashboard`, `isEmergencyPinned`, `isFavorite`, optional `createdAt`/`updatedAt`, `relatedRecords`, optional `drawingDataB64`, and `attachments`. Later compatible additions include optional `importantDateReminder` and `deletedAt`. Each related record has an `id` and optional `label`.

Attachments include `id`, `type`, `fileName`, `displayName`, `fileSize`, `orderIndex`, `dataB64`, and optional `thumbnailB64`/`derivedMetadataB64`. `dataB64` decodes to the attachment bytes as stored in HarborKeep. Derived metadata decodes to JSON that can include OCR text, suggested keywords/category/date, an image summary, source label, and analysis coverage. A shared attachment ID may occur on multiple records; preserve references while writing its bytes once. Reject conflicting bytes for one ID.

`fileName` is user-editable and may lack an extension or disagree with the actual encoding. Portable exporters detect PNG, JPEG, GIF, TIFF, BMP, WebP, HEIC/HEIF, AVIF, and PDF signatures before choosing the attachment's output suffix. Compatible suffixes (including `jpg`/`jpeg`/`jpe`, `tif`/`tiff`, and `heic`/`heif` aliases) are retained without changing case. Otherwise, append the detected suffix to the sanitized name. Unknown bytes keep an existing suffix or receive `.bin`. Preserve the original `fileName` and `displayName` in metadata and never re-encode the attachment to match its name. For ISO BMFF images, inspect a valid initial `ftyp` box's major and compatible brands within its first 4096 bytes, preferring AVIF, then HEIC, then generic HEIF brands.

## Native rich content

`notes` is not guaranteed to be human-readable plain text. It can contain a Base64 JSON note document with `version`, `richTextBase64`, `floatingImages`, and optional `sourceCanvasSize`. The rich text is RTF or RTFD data. Floating images contain PNG bytes and relative placement/rotation. Older notes can be direct Base64 RTF/RTFD, Base64 plain UTF-8, or plain strings. Preserve the stored value even when also producing a readable rendering.

`drawingDataB64` decodes to a native drawing document: JSON with `version`, Base64 PencilKit `drawingData`, `textItems`, and optional `backgroundImage`. Some older entries contain raw PencilKit bytes. Generic cross-platform software cannot assume it can render PencilKit strokes. Native data preserves editing information; PNG/PDF renderings provide independent visual readability.

## Optional portable rendering extension

New payloads can add **both** `portableFormatVersion: 1` and `portableFiles`. Existing app V1 readers ignore these unknown optional fields and continue restoring original record data. Removing either field from a purported portable payload makes it incomplete; an unknown portable version is unsupported.

Each `portableFiles` item contains:

```json
{
  "path": "records/11111111-1111-4111-8111-111111111111/record.pdf",
  "mediaType": "application/pdf",
  "dataB64": "<standard Base64 file bytes>",
  "sha256": "<64 lowercase hexadecimal SHA-256 characters>"
}
```

The SHA-256 is calculated over decoded file bytes. Every record needs `record.pdf`. Records with nonempty notes also need `notes.pdf` and `notes.txt`; records with native drawing data also need `drawing.png`. Plain text uses `text/plain` or `text/plain; charset=utf-8`. Paths must use a known record's lowercase UUID, must be relative, and must not contain traversal, backslashes, control characters, or aliases that collide on common filesystems. Renderings do not replace original structured records or editable source content. The complete portable extension is itself inside the authenticated encrypted payload.
