Skip to content

Encrypted Backups

ZestSSH backup files use the .zest format. All backups are encrypted — there is no option to export an unencrypted dump. Storing plaintext SSH credentials, private keys, and known hosts in user-accessible storage would be a serious security risk if the device is shared, lost, or scanned by another app.

This is a free feature — not paywalled.

Every .zest file has the following binary structure:

OffsetSizeContent
04 bytesMagic bytes: ZEST (ASCII)
41 byteVersion byte
5VariableEncrypted payload
VersionMeaning
1Legacy encrypted format (import-only): AES-256-GCM with an Argon2id-derived key, no parameter header. The app derives with the current default parameters and falls back to the older ones if that fails.
2Legacy unencrypted JSON (import-only, refused unless the user explicitly confirms an unprotected import)
3Encrypted (current): AES-256-GCM with an Argon2id-derived key, plus a small header recording the Argon2 parameters the backup was written under so it still decrypts after the app’s defaults change

The encrypted payload is structured as:

header_length(2) + argon2_params_header(JSON) + salt(32 bytes) + IV(12 bytes) + GCM tag(16 bytes) + ciphertext
  • Argon2 params header: JSON recording the memory/iterations/parallelism the backup was encrypted under.
  • Salt: 32 random bytes used for Argon2id key derivation.
  • IV: 12-byte AES-GCM initialization vector.
  • GCM tag: 16-byte authentication tag for integrity verification.
  • Ciphertext: zlib-compressed JSON, encrypted with AES-256-GCM.

Legacy version 1 files omit the parameter header.

  1. All app data is gathered via SyncDataService.gatherAll() into a JSON map.
  2. A 32-byte random salt is generated.
  3. The user’s backup password is run through Argon2id with the salt to derive a 256-bit key (same parameters as Juiced: 128 MB memory, 3 iterations, 4 parallelism).
  4. The JSON is zlib-compressed and encrypted with AES-256-GCM using the derived key.
  5. The output is prefixed with the ZEST magic bytes, version 3, and the Argon2-parameters header.
  • Minimum length: 12 characters
  • Empty or short passwords are rejected before any encryption occurs.
  • The 12-character minimum is enforced because backups land in user-accessible storage (Downloads folder, Files app), where the password is the only barrier against an offline attacker.

A backup contains everything needed to fully restore your ZestSSH configuration:

DataIncluded
Connections (host, port, settings, groups)Yes
Identities (usernames, auth methods)Yes
Identity secrets (passwords, private keys)Yes (encrypted)
Connection groupsYes
Known hosts (fingerprints)Stored in the file, but never applied on restore (each device re-learns host keys via TOFU)
SnippetsYes
Port forward rulesYes

Data that is not included:

  • Terminal transcripts / session recordings
  • Automation history / audit logs
  • App settings and preferences
  • Theme customizations
  1. Go to Settings > Backup & Restore > Export Backup.
  2. Enter a backup password (minimum 12 characters).
  3. ZestSSH encrypts all data and writes the .zest file.
PlatformLocation
Android/storage/emulated/0/Download/
iOSApplication Documents (accessible via the Files app)
Desktop (Windows/macOS/Linux)Documents/ZestSSH/backups/
  • Manual backups: zestssh_backup_YYYY-MM-DDTHH-MM-SS.zest
  • Auto-backups: zestssh_auto_YYYY-MM-DDTHH-MM-SS.zest
  1. Go to Settings > Backup & Restore > Import Backup.
  2. Select a .zest file.
  3. Enter the backup password.
  4. ZestSSH decrypts and restores the data.
  • Tombstone-aware merge: Import uses the same restore path as sync. Incoming records overwrite matching local records, new records are inserted, and the imported blob’s deletion tombstones are applied, so an import can also remove local records whose deletion it carries, not just add to them. Timestamped records are protected (a record survives an imported tombstone when the local copy is newer than the tombstone’s deletedAt), but timestamp-less configuration records are delete-wins. Importing an older backup can therefore roll back or delete newer local data.
  • Identity secrets are restored to Flutter Secure Storage after the database transaction succeeds, preventing orphaned secrets if the transaction rolls back.
  • Legacy v2 backups (unencrypted) are accepted for backward compatibility but trigger a UI warning urging the user to re-export with a password.

Files larger than 50 MB are rejected before loading into memory to prevent out-of-memory crashes from malicious or corrupted files.

ErrorCause
”Invalid backup file format”Missing or incorrect ZEST magic bytes
”This backup is password-protected”Encrypted file (version 1 or 3) but no password entered
”Wrong password or corrupted backup”AES-GCM decryption failed (wrong key or tampered data)
“Unsupported backup version”Unknown version byte
”Backup file is too large (max 50 MB)“File exceeds the safety limit
”Backup file not found”File path does not exist