Skip to content

Security Basics

ZestSSH is an SSH client, which means it handles private keys, passwords, and remote server access. This page covers the security features built into the app and how to configure them. All of these features are free — security is not paywalled.

You can set a numeric PIN that must be entered every time you open ZestSSH. Go to Settings > Security and choose PIN as the lock method.

Your PIN is never stored in plaintext. ZestSSH hashes it with Argon2id using the following parameters:

  • Memory: 4 MB
  • Iterations: 4
  • Parallelism: 2
  • Hash length: 32 bytes
  • Salt: 16 bytes, randomly generated per PIN

The stored value is base64(salt):base64(hash) in the platform’s secure storage. Argon2id is a memory-hard key derivation function that resists GPU and ASIC-based brute-force attacks.

After 10 consecutive wrong PIN entries, ZestSSH locks you out with escalating delays:

Failed attemptsLockout duration
1030 seconds
1560 seconds
20+5 minutes

The lockout state is persisted to secure storage. Restarting the app does not reset the lockout timer or the failed attempt counter. The counter resets only on a successful PIN entry.

PIN verification also uses constant-time comparison to prevent timing side-channel attacks.

If you set a PIN in an older version of ZestSSH (which used unsalted SHA-256), the hash is automatically migrated to Argon2id on your next successful unlock. No action is required.

If your device supports biometric authentication (fingerprint, face recognition), you can use it instead of or in addition to a PIN. Go to Settings > Security and choose Biometric or Both.

ZestSSH uses the local_auth package, which delegates to:

  • Android: BiometricPrompt API (fingerprint, face, iris).
  • iOS: Face ID or Touch ID.

Biometric data never leaves your device. ZestSSH does not store or process biometric templates — it asks the OS to verify your identity and receives a pass/fail result.

You can configure how quickly ZestSSH locks after you leave the app. The timeout is set in minutes (0 = lock immediately when backgrounded). This applies to both PIN and biometric lock methods.

By default your local database is encrypted with SQLCipher (AES-256), and the database key lives in your platform’s secure storage. This protects the data at rest, but on a computer the key is reachable by anything running as your user account.

Turn on Settings > Security > Encrypt data at rest to raise the bar: your PIN becomes the only way in. When it is on, the database key is wrapped behind your PIN, the plaintext key is removed from secure storage, and the database is closed whenever the app is locked. The wrapping uses Argon2id to derive a key from your PIN (4 MB on mobile, 64 MB on desktop, where there is no hardware rate limit), then AES-256-GCM, reusing the same cryptographic primitives as Cloud Sync. No custom cryptography is involved.

Because your PIN is the only key, there is no recovery if you forget it: restore a .zest backup or use Cloud Sync on a fresh install, or the local data is gone. Make a backup before you turn it on. Encryption at rest and the PIN are coupled: you cannot turn the PIN off while encryption is on.

On top of at-rest encryption, you can bind the key to your device’s secure hardware so that a copied database file is useless on any other machine. Enable it under Settings > Security > Hardware-backed encryption.

  • Android / iOS: a key held in the Android Keystore or the iOS Secure Enclave, released only after device authentication, wraps the database key. Unlocking then takes your app PIN plus a device-auth step.
  • Windows: a non-exportable key inside the TPM seals the wrapping key. The TPM key cannot leave the chip, so the encrypted data cannot be decrypted on a different computer even with the files in hand.

macOS and Linux do not have a hardware-binding option yet; use full-disk encryption there.

If the device’s secure hardware is reset (screen lock removed, device reset, or TPM cleared), the hardware-bound data cannot be decrypted, so keep a backup or Cloud Sync enabled.

ZestSSH uses the Trust On First Use model for host key verification. When you connect to a server for the first time, the app shows the server’s key type and SHA-256 fingerprint and asks you to accept or reject it.

Once you accept a host key, it is stored in ZestSSH’s known hosts database with:

  • Host address and port.
  • Key type (e.g., ssh-ed25519, ssh-rsa).
  • SHA-256 fingerprint.
  • First-seen and last-seen timestamps.

On subsequent connections, ZestSSH compares the server’s presented key against the stored fingerprint. If the key has changed, you see a warning. This could indicate a server reinstallation or a man-in-the-middle attack.

ZestSSH also detects key-type changes. If a server previously used ssh-rsa and now presents ssh-ed25519, that is treated as a changed key. This prevents an attacker from bypassing fingerprint checks by presenting a different algorithm.

Always verify host key fingerprints out-of-band (e.g., from your hosting provider’s dashboard or from a console session on the server) before accepting them. The TOFU model is only as strong as your first connection.

ZestSSH exports backup files with the .zest extension. These contain all your connections, identities, a known-hosts snapshot (kept in the file but not applied on restore), snippets, and connection groups.

All backups are encrypted with AES-256-GCM using a key derived from your backup password via Argon2id (same parameters as cloud sync). Encryption is the default and what we recommend. Since version 1.8 you can also opt out of a password: if you leave the backup password blank, the app warns you and asks you to confirm before it writes an unencrypted backup, for cases where you keep the file in an already-encrypted place or feed it into your own tooling. An unencrypted backup is a plaintext dump of your SSH credentials and private keys, so protect it accordingly.

When you set a backup password, it must be at least 12 characters; shorter passwords are rejected. Leaving it blank is the only way past that, and only after you confirm the warning.

A .zest file contains a serialized snapshot of:

  • All saved connections (hostname, port, username, authentication method).
  • All identity key pairs (private and public key material).
  • Known hosts database (fingerprints, key types), included in the file but deliberately not applied on restore. Each device re-learns host keys through its own Trust On First Use instead, so a stale or malicious backup cannot pre-seed a forged host key.
  • Snippets (saved commands).
  • Connection groups.

Go to Settings > Backups > Import and select a .zest file. Enter the backup password to decrypt and restore. If you enter the wrong password, decryption fails and no data is modified.

Restoring is not purely additive. A backup carries deletion tombstones along with its records, so importing one can remove or roll back local data, not just add to it. Timestamped records are protected (a local record survives an imported tombstone if it was edited more recently than the tombstone’s deletion time), but importing an older backup can still delete or revert newer local changes on records without that protection. Review what you are about to restore, especially when importing an older backup onto a device with newer data.

ZestSSH offers optional sync that keeps your data in step across devices. Sync is free on every tier with any storage you run (WebDAV, S3, Google Drive, or your own server); Juiced only adds the hosted ZestSSH Cloud storage. The full sync security model is covered in Zero-Knowledge Encryption, but the key points are:

  • All data is encrypted client-side before it leaves your device.
  • Encryption uses AES-256-GCM with a key derived from your sync password via Argon2id.
  • The server never sees your plaintext credentials, keys, or connection details.
  • Authentication uses Firebase Auth (Google or Apple sign-in) for identity verification on the hosted ZestSSH Cloud backend; your own storage needs no ZestSSH account.
  • You can change your sync password at any time; only the wrapped data key is re-encrypted, not every blob.

The sync server cannot decrypt your data. If you forget your sync password, use the recovery key issued when you set up sync; if you lose both, your synced data is unrecoverable by anyone, including us.