API Keys
API keys authenticate every automation request in ZestSSH. Whether a command is triggered from Tasker, MacroDroid, iOS Shortcuts, a deep link, or a scheduled task, a valid API key must be provided. Keys are designed to be secure by default with cryptographic generation, constant-time validation, rate limiting, and optional expiration.
Creating a Key
Section titled “Creating a Key”Navigate to Settings > Automation > API Keys and tap Generate New Key. You will be prompted for:
- Label: a descriptive name for the key (e.g., “Tasker”, “MacroDroid”, “Work Laptop”). Labels help you identify which key is used where.
- Expiration (optional): set the key to expire after 30, 60, or 90 days, or choose a custom date. Keys without an expiration never expire.
On creation, ZestSSH generates a 48-character alphanumeric key using Random.secure(), which is backed by the operating system’s cryptographically secure random number generator (/dev/urandom on Linux/Android, SecRandomCopyBytes on iOS/macOS, BCryptGenRandom on Windows). The character set includes lowercase letters (a-z), uppercase letters (A-Z), and digits (0-9), providing approximately 285 bits of entropy.
The key is displayed once for you to copy. After you leave the creation dialog, the full key is not shown again (only a masked version: first 4 characters, dots, last 4 characters).
Key Storage
Section titled “Key Storage”Keys are stored in FlutterSecureStorage, which uses:
- Android: EncryptedSharedPreferences (AES-256 backed by the Android Keystore).
- iOS: Keychain with
first_unlock_this_deviceaccessibility. - Windows/macOS/Linux: OS-specific credential stores.
Keys are serialized as a JSON array. Each key object includes: id (UUID), label, key (the raw 48-character string), createdAt, lastUsedAt (nullable), and expiresAt (nullable).
Validation
Section titled “Validation”When an automation request arrives, the API key is validated through this process:
1. Rate Limit Check
Section titled “1. Rate Limit Check”Before comparing any keys, the system checks for an active lockout. If the lockout timestamp has not expired, validation immediately returns false — no key comparison occurs. This prevents brute-force attacks from burning CPU on hash comparisons during lockout.
2. Constant-Time SHA-256 Comparison
Section titled “2. Constant-Time SHA-256 Comparison”The provided key is hashed with SHA-256. Each stored key is also hashed with SHA-256. The two 32-byte hash arrays are compared using constant-time XOR:
result = 0for i in range(length): result |= a[i] ^ b[i]return result == 0This prevents timing attacks where an attacker could measure response time to determine how many bytes of a key are correct.
3. Expiration Check
Section titled “3. Expiration Check”If a matching key is found, its expiresAt timestamp is checked. Expired keys are treated as invalid and count toward the failed-attempt counter (not reset).
4. Result Handling
Section titled “4. Result Handling”- Valid key: the failed-attempt counter resets to 0, the key’s
lastUsedAtis updated, and the automation proceeds. - Invalid key: the failed-attempt counter increments by 1. If the counter reaches 5, a 1-minute lockout is activated and the counter resets. Both the counter and lockout timestamp are persisted to FlutterSecureStorage, surviving app restarts.
Atomic Validation
Section titled “Atomic Validation”The validateAndMarkUsed() method performs validation and lastUsedAt update in a single operation, eliminating a potential race condition between separate isValidKey() and markUsed() calls.
Multiple Keys
Section titled “Multiple Keys”You can create any number of API keys. This allows you to:
- Issue separate keys to different automation apps (Tasker, MacroDroid, Shortcuts).
- Revoke a single key without disrupting other automations.
- Set different expiration periods for different use cases.
- Audit which key was used last (via the
lastUsedAttimestamp).
Managing Keys
Section titled “Managing Keys”The API Keys screen shows all keys with:
- Label and creation date.
- Masked key (first 4 + dots + last 4 characters).
- Last used timestamp (if ever used).
- Expiration status (“Never expires”, “Expires in N days”, “Expired”).
Revoking a Key
Section titled “Revoking a Key”Tap a key to access its options, then tap Revoke. The key is immediately deleted from secure storage. Any automation using that key will fail on the next attempt. Revocation is instant and cannot be undone — you would need to generate a new key and update your automation configurations.
Renaming a Key
Section titled “Renaming a Key”You can change a key’s label without regenerating it. This is useful when you move an automation to a different app or want to update the description.
Scheduled Task Key References
Section titled “Scheduled Task Key References”When scheduling tasks via WorkManager (Android), ZestSSH stores only the key’s ID (UUID), not the raw key string. At execution time, the scheduled task passes the ID to the automation handler, which looks up the actual key from FlutterSecureStorage. This means the raw key is never written to WorkManager’s internal database or persisted in plain text on disk.
Best Practices
Section titled “Best Practices”- One key per app: create separate keys for each automation app (Tasker, MacroDroid, etc.) so you can revoke one without affecting others.
- Set expiration dates: for temporary automations or shared devices, set keys to expire after 30 or 90 days.
- Revoke unused keys: regularly review the API Keys screen and revoke any keys that show no recent
lastUsedAttimestamp. - Keep keys private: the
zestssh://URLs contain the raw API key. Do not share these URLs publicly or commit them to version control. - Monitor automation history: check the automation log for unexpected activity that might indicate a compromised key.