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.
File Format
Section titled “File Format”Every .zest file has the following binary structure:
| Offset | Size | Content |
|---|---|---|
| 0 | 4 bytes | Magic bytes: ZEST (ASCII) |
| 4 | 1 byte | Version byte |
| 5 | Variable | Encrypted payload |
Version Byte
Section titled “Version Byte”| Version | Meaning |
|---|---|
1 | Legacy 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. |
2 | Legacy unencrypted JSON (import-only, refused unless the user explicitly confirms an unprotected import) |
3 | Encrypted (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 |
Encrypted Payload (Version 3, current)
Section titled “Encrypted Payload (Version 3, current)”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.
Encryption Process
Section titled “Encryption Process”- All app data is gathered via
SyncDataService.gatherAll()into a JSON map. - A 32-byte random salt is generated.
- 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).
- The JSON is zlib-compressed and encrypted with AES-256-GCM using the derived key.
- The output is prefixed with the
ZESTmagic bytes, version3, and the Argon2-parameters header.
Password Requirements
Section titled “Password Requirements”- 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.
What’s Included
Section titled “What’s Included”A backup contains everything needed to fully restore your ZestSSH configuration:
| Data | Included |
|---|---|
| Connections (host, port, settings, groups) | Yes |
| Identities (usernames, auth methods) | Yes |
| Identity secrets (passwords, private keys) | Yes (encrypted) |
| Connection groups | Yes |
| Known hosts (fingerprints) | Stored in the file, but never applied on restore (each device re-learns host keys via TOFU) |
| Snippets | Yes |
| Port forward rules | Yes |
Data that is not included:
- Terminal transcripts / session recordings
- Automation history / audit logs
- App settings and preferences
- Theme customizations
Export Flow
Section titled “Export Flow”- Go to Settings > Backup & Restore > Export Backup.
- Enter a backup password (minimum 12 characters).
- ZestSSH encrypts all data and writes the
.zestfile.
File Location by Platform
Section titled “File Location by Platform”| Platform | Location |
|---|---|
| Android | /storage/emulated/0/Download/ |
| iOS | Application Documents (accessible via the Files app) |
| Desktop (Windows/macOS/Linux) | Documents/ZestSSH/backups/ |
Filename Format
Section titled “Filename Format”- Manual backups:
zestssh_backup_YYYY-MM-DDTHH-MM-SS.zest - Auto-backups:
zestssh_auto_YYYY-MM-DDTHH-MM-SS.zest
Import Flow
Section titled “Import Flow”- Go to Settings > Backup & Restore > Import Backup.
- Select a
.zestfile. - Enter the backup password.
- ZestSSH decrypts and restores the data.
Import Behavior
Section titled “Import Behavior”- 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.
Import Size Limit
Section titled “Import Size Limit”Files larger than 50 MB are rejected before loading into memory to prevent out-of-memory crashes from malicious or corrupted files.
Error Handling
Section titled “Error Handling”| Error | Cause |
|---|---|
| ”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 |