-
Notifications
You must be signed in to change notification settings - Fork 18
5. Advanced Topics — Security
This section outlines the internal architecture on which the app is built. SecureFolderFS employs a layered encryption and authentication model to maintain both the integrity and confidentiality of your data. The login process may vary slightly depending on the chosen authentication method.
If you selected a password or key file for authentication, the contents of the secret sequence form the passkey — the combined output of all active authentication factors. Since Vault V4, the passkey is no longer fed directly into the key derivation function. Instead, the Key Encryption Key (KEK) is derived in several steps:
-
Bootstrap key — A 32-byte bootstrap key is derived from the raw passkey using HKDF-SHA256, with the salt value retrieved from the keystore file and the
SFFSv4-EntropyBootstrap-v1context label. Because the bootstrap key is derived from the raw passkey, recovering the software entropy (next step) always requires every active authentication factor. -
Software entropy — A 32-byte value generated by a CSPRNG when the vault is created (or whenever credentials are rotated to fresh entropy). It is stored in the keystore file, encrypted with AES-GCM under the bootstrap key. During unlock, it is decrypted using the bootstrap key. Software entropy raises the effective key-material strength to a full 256 bits regardless of the entropy of the chosen authentication factor.
-
Augmented passkey — The raw passkey and the decrypted software entropy are mixed together using HKDF-SHA256 (
SFFSv4-AugmentedPasskey-v1). Compromising either value alone is insufficient to reproduce the augmented passkey. -
KEK derivation — The augmented passkey is processed by the Argon2id Key Derivation Function (KDF), together with the keystore salt, to produce the KEK. Since Vault V3, Argon2id uses the following parameters:
- 32MiB of allocated memory size
- 2 parallelism lanes
- 2 cipher iterations
-
Key unwrapping — The resulting KEK unwraps the Data Encryption Key (DEK) and Message Authentication Code (MAC) keys using the AES Key Wrap algorithm (RFC 3394).
If you have selected Windows Hello or Android Biometrics as one of your authentication options, a challenge value read from a separate configuration file is signed by a hardware-protected, non-exportable private key. The resulting cryptographic signature becomes the factor's key material, which forms (or contributes to) the passkey entering the derivation pipeline described above.
-
Android Biometrics: On Android, the non-exportable RSA private key pair is stored in the Android KeyStore, protected by your biometric attestation. During the authentication process, the challenge is signed (using RSASSA-PKCS1-v1_5 + SHA256) and passed into the KDF.
-
Windows Hello: On Windows, the non-exportable RSA private key pair is stored within the device’s TPM module. The cryptographic signature is passed into the KDF.
If you have selected biometric authentication on an Apple device, a secret key material is used as the main parameter for key derivation. Contrary to the challenge mode, the “secret key” is encrypted and stored in a separate configuration file.
- Face ID and Touch ID: On Apple platforms, the non-exportable private key pair is stored in the Keychain. During the authentication routine, encryption and decryption operations take place within the Secure Enclave. The key itself is protected with Elliptic Curve cryptography (secp256r1 curve for ECIES with X9.63 KDF + SHA256 and AES-GCM).
If a second authentication method is configured, its resulting key is appended (chained) to the primary key. This combined key becomes the passkey, which then follows the standard derivation pipeline described above — bootstrap key, software entropy, augmented passkey, and finally the Argon2id-derived KEK that unwraps the DEK and MAC keys. Because both keys are required to assemble the passkey, the resulting strength is the combination of both methods, and unlocking always requires both.
Instead of chaining a second-stage method, you may configure it as a complement to the primary method. In this mode, either credential alone unlocks the vault — you are asked for one of them at login, not both. This is an availability trade-off made by design: because each method is independently sufficient, the effective security equals that of the weaker method. If you want the combined strength of two methods, chain them as a second stage instead.
Complementation works by keying the vault to a 32-byte complement secret which acts as the passkey for the standard derivation pipeline described above:
-
Primary method — The complement secret is derived directly from the primary credential using HKDF-SHA256, with the vault's unique identifier as the salt and the primary method's identifier (together with the current complementation generation, see below) as the context. No additional material needs to be stored for this path, so holding the primary credential is always sufficient.
-
Complement method — The same complement secret is escrowed in a separate share file (
sfcomplement.cfg). It is encrypted with AES-GCM under a wrap key derived from the complement credential via HKDF-SHA256, using the same vault-bound context (vault identifier, complement method identifier, and generation).
At login, the provided credential is first treated as the primary (direct derivation); if that fails, it is used to decrypt the share file. Both paths are authenticated operations — an incorrect credential fails both and reveals nothing. The recovered complement secret then enters the regular V4 pipeline (bootstrap key, software entropy, augmented passkey, Argon2id), so all keystore guarantees apply unchanged.
Each vault carries a monotonic complementation generation counter in its configuration file, protected by the configuration signature. The counter is mixed into every complement-related key derivation and is incremented whenever complementation is enabled, the complement credential is replaced, or the primary credential changes. Incrementing the generation produces an entirely new complement secret and re-encrypts the keystore under it, which means a replaced or revoked complement credential — even combined with an old copy of the share file recovered from a backup or file history — can no longer unlock the vault. The counter is never decreased: it is preserved even while complementation is disabled, so re-enabling the feature can never reuse a previously issued generation.
Every vault provides a recovery key, which can be exported during vault creation or later from the vault's properties. The recovery key is not an additional credential — it is the Base64-encoded master key pair (the DEK and MAC keys) itself, the very material that every derivation pipeline above ultimately protects.
Two properties follow directly from this:
-
It bypasses all configured authentication. The recovery key unlocks the vault regardless of the configured methods — chained second stages, complementation, and its generation counter included. It is the designated fallback when an authentication factor becomes unavailable (for example, a lost key file or a deleted authentication configuration file).
-
It is never invalidated by credential changes. Changing, chaining, or rotating authentication methods re-wraps the same DEK and MAC keys under new credentials; the keys themselves do not change. A recovery key exported once remains valid for the lifetime of the vault.
For these reasons, the recovery key must be stored with the same care as the vault contents themselves — preferably offline, outside the folder hierarchy where the vault is stored or synchronized.
To ensure the authenticity of the configuration file, its properties — including the configured authentication scheme and the complementation generation — are cryptographically signed using the unwrapped MAC key. The resulting HMAC-SHA256 signature is then compared to the one stored in the file. If both hashes match, the vault is considered genuine. This prevents an attacker with access to the vault files from silently altering how the vault authenticates, such as swapping authentication methods or rolling back the complementation generation.
This section describes the encryption steps of the XChaCha20-Poly1305 (Extended Nonce) AEAD cipher mode.
Each written-to file is initialized with a unique 72-byte file header, composed of the following components:
| Component | Size | Description |
|---|---|---|
| Cryptographic Nonce | 24 B | Used as the extended nonce for XChaCha20 |
| Block Encryption Key | 32 B | CSPRNG key used to encrypt the file blocks |
| MAC Authentication Tag | 16 B | Poly1305 tag used for authentication |
The block encryption key (32 bytes) is encrypted using XChaCha20-Poly1305 supplemented with the vault's DEK key and an empty AAD parameter, with the cryptographic nonce passed as part of the encryption process. The resulting ciphertext is stored in the header, with the nonce prepended (first 24 bytes) and the MAC tag appended (last 16 bytes). In total, the ciphertext header consists of 72 bytes.
After a file's header is initialized and stored, the file's contents are encrypted in fixed-sized blocks using the XChaCha20-Poly1305 authenticated encryption algorithm. Each block can contain a maximum of 32KiB (32,768 bytes) of plaintext data. For the final block, the data is not padded or extended to meet the size limit — only the remaining bytes are encrypted.
During encryption, each plaintext block is transformed into a ciphertext block with the following layout:
| Component | Size | Description |
|---|---|---|
| Block Nonce | 24 B | A unique nonce generated using CSPRNG |
| Encrypted Data | up to 32 KiB | Ciphertext resulting from encryption of the block |
| Authentication Tag | 16 B | Poly1305 MAC tag for verifying integrity |
-
For each block, a unique 24-byte nonce is generated using a cryptographically secure pseudorandom number generator (CSPRNG) whenever a change is registered in that block.
-
To bind each block cryptographically to its context, the following is used as Additional Authenticated Data (AAD):
- The block number (sequential index), encoded in big-endian format
- The nonce from the file header
These two values are concatenated to form the AAD input to the cipher.
- The block is then encrypted using XChaCha20-Poly1305 with the following parameters:
- Key: the 32-byte header block key imported from the decrypted file header
- Nonce: the per-block unique 24-byte nonce
- AAD: the concatenated additional authentication data
- The final ciphertext block is prepended with a nonce (first 24 bytes) and appended with a MAC tag (last 16 bytes), equating to a 40-byte larger data imprint than its plaintext counterpart.
By default, SecureFolderFS uses AES-CMAC-SIV with Base4K to encrypt and encode file names. Each directory in the file system has an associated Initialization Vector (IV), stored in a special file, named dirid.iv. The exception is the root directory (i.e., the "content" folder), where the IV is always considered empty.
During the encryption process, the IV is read from the corresponding dirid.iv file and used as Additional Authenticated Data (AAD). The AES-CMAC-SIV instance is keyed with the vault’s DEK and MAC keys, which together form the double-length key the SIV construction requires (one half for S2V/CMAC authentication, the other for AES-CTR encryption). The plaintext name is then encrypted using this configuration.
Depending on the option chosen during vault creation, the resulting ciphertext is encoded with either Base4K or Base64Url encoders, and a ".sffs" extension is appended to the final ciphertext name.
SecureFolderFS protects the confidentiality and integrity of file contents and names. Like other designs of this class, certain properties remain observable or unverifiable by design, and you should be aware of them when storing a vault on untrusted or shared storage:
-
Metadata visibility. An observer with access to the encrypted folder can see the number of files, the shape of the directory tree, approximate file sizes (plaintext size is derivable from ciphertext size), and file timestamps.
-
Name encryption is deterministic within a directory. Encrypting the same name in the same directory always produces the same ciphertext — a requirement for file lookups. Identical names in different directories produce unrelated ciphertexts, since each directory's IV participates in the encryption.
-
Whole files can be exchanged. File headers are not cryptographically bound to their encrypted names or locations. An attacker with write access to the vault folder could swap two encrypted files, causing each to decrypt under the other's name. Within a file, however, the per-block AAD (block index and header nonce) prevents blocks from being reordered or transplanted between files.
-
Rollback is not detected. Replacing an encrypted file (or an individual block) with an older version of itself still passes authentication, as no version information participates in the encryption. If you require protection against rollback, keep independent versioned backups.