Encryption

How Server Sidekick protects your data, and the exact format of every file it encrypts. Published October 13, 2026.

Summary

  • Only published, standard algorithms. Everything the app encrypts uses algorithms published by NIST or the IETF: AES-256-GCM, ChaCha20-Poly1305, SHA-2, HMAC, HKDF, Argon2id, PBKDF2, Ed25519 and the SSH and TLS protocols. Nothing is proprietary or secret. The list, with its standards.
  • The algorithms are fixed. The app has no setting for choosing ciphers and no way to plug in other ones.
  • Encrypted files don't rely on a secret format. This page describes each format exactly, so anyone can check what the app does. Their security rests on your passphrase, or for an invitation on a one-time code, alone.

Where the app uses cryptography

WhatAlgorithms
SSH and SFTP connections to your servers, including port forwarding and jump hostsThe SSH protocol. Ciphers aes256-gcm, aes128-gcm, chacha20-poly1305, aes256-ctr and aes128-ctr; key exchange curve25519-sha256, ECDH over NIST P-521, P-384 and P-256, and Diffie-Hellman (group exchange and group 14, both with SHA-256); host keys Ed25519, RSA with SHA-512 or SHA-256, and ECDSA; MACs HMAC-SHA-256 and HMAC-SHA-512 (encrypt-then-MAC preferred), and HMAC-SHA-1. The app and your server negotiate from this fixed list.
SSH keysKeys the app generates are Ed25519 (the default), ECDSA over NIST P-256, P-384 or P-521, or RSA with 3072 or 4096 bits. Keys you import are read in the standard OpenSSH and PEM private-key formats, including passphrase-protected ones.
Checking a server's host keySHA-256 fingerprints. On the Mac, hashed names in your ~/.ssh/known_hosts are matched with HMAC-SHA-1, as OpenSSH writes them.
HTTPS connections (Proxmox, a WebDAV sync server, a server's BMC, the optional assistant)TLS, through the BoringSSL library built into the Dart runtime. A certificate your device doesn't trust is accepted for a sync server or a BMC only if its SHA-256 fingerprint matches one you chose to trust.
Web admin pages opened in the app's web viewYour device's own TLS (WebKit on Apple platforms, the Android System WebView on Android), inside the SSH tunnel. A certificate your device doesn't trust is accepted only if its SHA-256 fingerprint matches one you chose to trust.
Passwords and SSH keys stored on your device, alert destinations, and saved configuration-drift snapshotsAES-256-CBC with PKCS#7 padding and a random IV for each entry, under a random 256-bit key that is kept in the Keychain (Apple platforms) or the Keystore (Android)
Other secrets on your device (sync's keys and server password, BMC logins, backup repository passwords, the assistant's API key)Kept in the Keychain or the Keystore directly
Encrypted backups and "Send credentials" filesArgon2id and AES-256-GCM (format below). Older files: PBKDF2-HMAC-SHA256 and AES-256-GCM (below)
SyncArgon2id, HKDF-SHA256, HMAC-SHA256 and AES-256-GCM (format below)
App lockYour device's own Face ID, Touch ID, fingerprint or passcode check

Backup and credential files (SSKB2)

Settings → Backup & Restore and Credentials → Send credentials both write this format. A backup's file name ends in .sskb, a credential file's in .sskc. The file holds one JSON document, encrypted with a key derived from a passphrase you choose. The passphrase must be at least 8 characters, and the app never stores it.

All numbers are unsigned and big-endian. The offsets are those of a file the app writes today, with a 16-byte salt.

OffsetBytesField
05Magic: the ASCII text SSKB2
51Key derivation ID: 1 means Argon2id
61Length of the key derivation's settings: 12 for Argon2id
74Argon2id memory, in KiB (65536, which is 64 MiB)
114Argon2id passes (3)
154Argon2id lanes (4)
191Salt length (16)
2016Salt, random for every file
361Cipher ID: 1 means AES-256-GCM
3712AES-GCM nonce, random for every file
49rest − 16Ciphertext
end − 1616AES-GCM authentication tag
  • Key: Argon2id (RFC 9106, version 0x13) with the memory, passes and lanes in the header (64 MiB, 3 and 4 today, RFC 9106's second recommended setting), the header's salt, and a 32-byte output. The password is the UTF-8 encoding of your passphrase after Unicode NFC normalization, so the same passphrase typed on different keyboards gives the same key. There is no secret value or associated data. This is the same derivation sync uses for its keyring.
  • Cipher: AES-256-GCM (NIST SP 800-38D) with a 12-byte nonce and a 16-byte tag. The associated data is the whole header, every byte before the ciphertext (49 bytes today), so no header byte can be changed without the tag failing.
  • What's inside: UTF-8 JSON. A backup is an object with "kind": "full_backup", "version": 1, the time it was made (exportedAt), and your credentials, groups, hosts, portForwards, snippets and alertDestinations. A credential file has "kind": "credentials", "version": 1, exportedAt and only the credentials you picked, each with its name, username, type, password, private key, key passphrase and privilege setting.
  • Limits when opening a file: the app opens a file only if its settings are within these bounds: memory at most 256 MiB and at least 8 KiB per lane, 1 to 16 passes, 1 to 16 lanes, and a salt of 16 to 64 bytes. A file with an unknown key derivation or cipher ID, or with settings above those bounds, is reported as made by a newer version and not opened. So the settings can be raised later without a new format.

A wrong passphrase and a damaged or altered file both fail the authentication tag, and the app can't tell them apart. Nothing is restored from a file that fails.

Versions of the app from before SSKB2 can't open SSKB2 files. They report that the file doesn't look like a backup, and change nothing.

Older backup files (SSKB1)

The app still opens SSKB1 files, and always will, but no longer writes them. Earlier versions wrote backups and credential files in this format. The JSON inside is the same as above.

BytesField
5Magic: the ASCII text SSKB1
16Salt, random for every file
12AES-GCM nonce, random for every file
16AES-GCM authentication tag
restCiphertext
  • Key: PBKDF2 with HMAC-SHA256 (RFC 8018), 210,000 iterations, a 32-byte output. Its inputs are the salt and the UTF-8 bytes of the passphrase exactly as typed, without normalization, as those files were made.
  • Cipher: AES-256-GCM, with no associated data. Note that the tag comes before the ciphertext in this format.

Sync

Sync keeps your hosts and related records the same on your devices, through a WebDAV server you choose. (The privacy policy lists what syncs.) The server only ever receives encrypted files, plus one unencrypted file that holds nothing secret. The sync folder looks like this:

server-sidekick-sync/
  keyring.json              the data key, encrypted with your passphrase
  devices/<deviceId>.ssks   one encrypted file per device

Setting up sync creates a random 256-bit data key and a random 128-bit key ID for it. Every device file is encrypted under a key derived from the data key. Each device keeps the data key in its secure storage, and it is stored on the server only after being encrypted with a key derived from your sync passphrase. The sync passphrase must be at least 15 characters, counted as Unicode code points after NFC normalization. It never leaves your devices and is not stored.

The keyring (keyring.json)

A JSON object. Byte strings are standard Base64 with padding.

{
  "format": 1,
  "keyId": "<16 bytes>",
  "kdf": {"alg": "argon2id", "m": 65536, "t": 3, "p": 4, "salt": "<16 bytes>"},
  "wrappedKey": "<nonce (12) | ciphertext (32) | tag (16)>"
}
  • Key-encryption key: Argon2id (RFC 9106, version 0x13). The defaults are 64 MiB of memory (m, in KiB), 3 passes (t) and 4 lanes (p), RFC 9106's second recommended setting. It uses the 16-byte salt and makes a 32-byte output, with no secret value or associated data. The password is the UTF-8 encoding of your passphrase after Unicode NFC normalization. The settings are stored in the file, so they can be raised later without changing the format. A device uses a keyring only if its settings are within these bounds: memory at most 1 GiB and at least 8 KiB per lane, 1 to 16 passes, and 1 to 16 lanes.
  • wrappedKey: the data key, encrypted with AES-256-GCM under the key-encryption key and a random 12-byte nonce. The associated data is the ASCII text ssk-sync-keyring-v1 followed by the 16 bytes of the key ID.
  • While a key is being replaced (Settings → Sync → Rotate key), the keyring briefly also has "previous": {"keyId", "wrappedKey"}. That is the old data key, wrapped under the same key-encryption key with the associated data ssk-sync-keyring-previous-v1 | the old key ID. Every file on the server stays readable until each device has re-encrypted its own.
  • After a key has been replaced, the keyring can have "replaced": {"keyIds": [...], "mac": "<32 bytes>"}. That is up to 8 earlier key IDs, newest first. Your other devices use it to recognise the new key as a replacement and carry on without setting up again. mac is HMAC-SHA256 over ssk-sync-keyring-replaced-v1 | key ID | each listed key ID in order. Its key is HKDF-SHA256 (RFC 5869) of the data key, with no salt (RFC 5869's default), the info string ssk-sync-keyring-replaced-v1 and 32 bytes of output. Only a device that unlocked the data key with your passphrase can check or write that list.

Device files (SSKS1)

Each device writes only its own file, devices/<deviceId>.ssks. The app makes each device ID a random UUID (version 4); a reader accepts any ID of 1 to 64 characters from A–Z a–z 0–9 _ -.

BytesField
5Magic: the ASCII text SSKS1
16Key ID of the data key the file is encrypted under
32File salt, random for every write
12AES-GCM nonce, random for every write
rest − 16Ciphertext
16AES-GCM authentication tag
  • File key: HKDF-SHA256 (RFC 5869) of the data key, with the file salt, the info string ssk-sync-device-file-v1 and 32 bytes of output. Every write therefore has its own key.
  • Cipher: AES-256-GCM. The associated data is SSKS1 | key ID | the UTF-8 device ID taken from the file's name. A file copied or renamed to another device's name fails to decrypt, and so does one encrypted under another key.
  • Plaintext and padding: the payload's length as a 4-byte big-endian number, then the payload, then zero bytes. The zero bytes make the whole file a power of two in size, at least 4 KiB, so its size reveals only roughly how much you sync. The file's 81 bytes of header and tag are counted in that size. Files over 64 MiB are neither written nor read, and a reader rejects padding that isn't all zeros.
  • Payload: a UTF-8 JSON object with the format version ("format": [1, 1]), the device ID (device), a write counter (seq), the writer's clock in milliseconds (clock) and the synced records (records). It can also have the device's display name (name): the device model, or a name you gave it in Settings → Sync, never your device's own name. Because the name sits inside the encrypted payload, the server never sees it.

A torn or tampered file fails its authentication tag. It is skipped, never partly applied. A file whose write counter is lower than one already seen from that device is ignored as a rollback.

Adding a device with a code (SSKI1)

A device that already syncs can show a QR code (Settings → Sync → Add a device) that lets a new device join without typing the sync passphrase. The code holds a one-time secret; the data key goes to the server encrypted under a key derived from that secret, so neither the code nor the server's copy opens anything on its own.

The code is this text, as a QR code or copied, with each value URL-encoded:

server-sidekick-sync:join?v=1&url=<sync folder address>&user=<username>&cert=<certificate fingerprint>&id=<invitation ID>&secret=<secret>
  • id is 16 random bytes and secret is 32 random bytes, each written in URL-safe Base64 without padding (22 and 43 characters).
  • cert is present only if the inviting device trusts the server's certificate by its fingerprint: the certificate's SHA-256 as 64 hex digits.
  • The code never holds the sync passphrase, the server's password or the data key.

The invitation file is invites/<id>.sski in the sync folder:

BytesField
5Magic: the ASCII text SSKI1
12AES-GCM nonce, random
rest − 16Ciphertext
16AES-GCM authentication tag
  • Key: HKDF-SHA256 (RFC 5869) of the code's 32-byte secret, with the invitation ID's 22 characters (as ASCII text, not the decoded bytes) as the salt, the info string ssk-sync-invite-v1 and 32 bytes of output.
  • Cipher: AES-256-GCM. The associated data is SSKI1 | the invitation ID's 22 characters, so a file renamed to another ID fails to decrypt.
  • Plaintext: UTF-8 JSON: {"v": 1, "keyId": "<16 bytes>", "key": "<32 bytes>", "expires": <milliseconds since 1970>}, with the data key and its ID in standard Base64.
  • Lifetime: an invitation expires 10 minutes after it is made, by the inviting device's clock; the new device allows up to 5 minutes more for clocks that disagree. The new device deletes the file before it uses the key, and the inviting device deletes it when its dialog closes or once it has expired.

Standards and libraries

The app uses only these algorithms, each as its standard defines it. All of them run in code built into the app.

AlgorithmStandardWhere, and which library
AES-256-GCMFIPS 197 (AES); NIST SP 800-38D (GCM)Backups, sync, invitations: cryptography 2.9.0. SSH: dartssh2 4.1.0
Argon2id, version 0x13RFC 9106Backups and the sync keyring: cryptography 2.9.0
HKDF-SHA256RFC 5869Sync file keys, the keyring's key list, invitations: cryptography 2.9.0
HMAC-SHA256, HMAC-SHA512RFC 2104 and FIPS 198-1; SHA-2 per FIPS 180-4The keyring's key list: cryptography 2.9.0. SSH: dartssh2 4.1.0. Hashes of synced values kept on the device: crypto 3.0.7
SHA-256, HMAC-SHA-1FIPS 180-4; RFC 2104Host-key and certificate fingerprints, OpenSSH's hashed host names, SSH: crypto 3.0.7, dartssh2 4.1.0
PBKDF2-HMAC-SHA256RFC 8018Opening older SSKB1 backups only: cryptography 2.9.0
AES-256-CBC, PKCS#7 paddingFIPS 197; NIST SP 800-38A (CBC); RFC 5652 (padding)Passwords and keys stored on the device: hive_ce 2.20.0
AES-CTR, ChaCha20-Poly1305NIST SP 800-38A; RFC 8439SSH: dartssh2 4.1.0, on pointycastle 4.0.0
Curve25519 key exchange, Ed25519RFC 7748, RFC 8032, RFC 8731, RFC 8709SSH: dartssh2 4.1.0, on pinenacl 0.6.0. Generating SSH keys: cryptography 2.9.0
ECDH and ECDSA over NIST P-256, P-384 and P-521; RSA signatures; Diffie-HellmanFIPS 186-5; RFC 5656, RFC 8332, RFC 4419, RFC 8268SSH: dartssh2 4.1.0, on pointycastle 4.0.0. Generating ECDSA and RSA SSH keys: pointycastle 4.0.0
The SSH protocolRFC 4251 to 4254 and the RFCs above; OpenSSH's published specifications for its aes-gcm, chacha20-poly1305 and encrypt-then-MAC variantsdartssh2 4.1.0
TLSIETF TLS (RFC 8446 and earlier versions)The Dart runtime's built-in BoringSSL; in the web view, the platform's own TLS
Unicode NFC normalizationUnicode Standard Annex #15Passphrases for backups and sync: unorm_dart 0.3.2

Every random key, salt, nonce and ID in the files described on this page comes from the operating system's secure random number generator, through Dart's Random.secure().

Notation

  • | means the byte strings are joined end to end.
  • Text written in code inside a key derivation or associated data means its ASCII bytes, with no terminator or length prefix.
  • Lengths are in bytes unless they say otherwise. KiB is 1,024 bytes and MiB is 1,048,576 bytes.

Questions about this page, or a security problem to report: see Support.