Skip to content

Key Management

Elman Mohammadi edited this page Oct 6, 2026 · 1 revision

Key Management

V14 uses a layered key hierarchy designed to separate user credentials, the package root key, metadata keys, filename keys, and per-file keys.

Key hierarchy

Password ------------------+
                            |
                         Argon2id13
                            |
Pattern -------------------+
                            |
                     credentialWrapKey
                            |
                            v
                  +---------------------+
                  | primary key slot    |
                  | AEAD-wrapped root   |
                  +---------------------+
                            |
                            v
                    packageRootKey
                            |
              +-------------+-------------+
              |             |             |
              v             v             v
         manifestKey    filenameKey   fileRootKey
                                          |
                                +---------+---------+
                                |         |         |
                                v         v         v
                              fileKey   fileKey   fileKey

The recovery path is parallel:

random recoveryKey
        |
        v
recovery key slot
        |
        v
same packageRootKey

Password and pattern

The application requires strong password and pattern values.

Password policy

The current validator requires:

  • at least 12 characters
  • lowercase letters
  • uppercase letters
  • numeric characters
  • special characters
  • estimated entropy of at least 60 bits according to the application's heuristic

Pattern policy

The pattern requires:

  • at least 12 characters
  • valid UTF-8
  • no null byte
  • no single-character repetition of the entire pattern
  • at least 5 distinct characters
  • estimated entropy of at least 50 bits according to the application's heuristic

These checks are policy controls, not replacements for a strong password manager or proper secret handling.

Credential derivation

The user password and pattern are processed separately through Argon2id13. They use different purpose-derived salts even though the package header carries one 32-byte package salt.

Intermediate derived keys are zeroized after they are combined into the credential wrapping key.

Why use a random package root key?

A random package root key creates a clean boundary:

  • credentials are used to unlock the package key;
  • the package key becomes the parent for all package-specific cryptographic subkeys;
  • individual credentials do not become the long-lived file encryption key.

This is a more modular design than repeatedly treating a password-derived value as the direct content key.

Recovery key

The optional recovery key is an independently generated 256-bit secret.

The project encodes it for transport as unpadded base64url. The reader expects a 43-character URL-safe form and decodes it back to exactly 32 bytes.

The recovery key should be:

  • stored offline or in another secure secret manager;
  • kept separate from the package;
  • excluded from logs and analytics;
  • excluded from source control;
  • treated like a full package credential.

The package itself does not contain a recoverable copy of the recovery key.

What happens when credentials are wrong?

The primary key slot fails AEAD authentication if the derived credential wrapping key is incorrect.

The API also rate-limits package opening attempts. Failed operations use generic error messages so the caller does not receive detailed information about which internal validation stage failed.

Secrets in memory

The implementation calls sodium_memzero() for sensitive values such as:

  • password-derived intermediate keys;
  • package root key;
  • manifest key;
  • filename key;
  • file root key;
  • per-file keys;
  • recovery key bytes;
  • user-supplied password/pattern buffers in controller paths where appropriate.

Zeroization is best understood as a defense-in-depth measure. High-level language runtimes cannot provide an absolute guarantee that no copies ever existed.

Server name pepper

Project names and account usernames can be protected with a server-side secret called SPK_NAME_PEPPER.

The application supports two modes:

  1. Explicit secret: provide SPK_NAME_PEPPER through controlled server configuration.
  2. Local bootstrap fallback: when it is absent, the application creates a persistent random 256-bit secret under storage/secrets/name-pepper.bin with restrictive permissions.

The fallback exists to make ordinary Apache/XAMPP setups functional. Controlled production secret management is preferred.

Account password storage

Application account passwords are stored as PHP PASSWORD_ARGON2ID password hashes, separate from the V14 package encryption KDF.

The account verifier also performs a dummy Argon2id verification when the username does not exist to reduce timing differences associated with username enumeration.

This is distinct from V14 package encryption. Application login credentials and package decryption credentials are different concepts.

Key lifecycle summary

Generate
  -> derive/wrap
  -> use only for intended purpose
  -> zeroize sensitive intermediates
  -> retain only the non-secret metadata required by the package format

The key-management design is intentionally explicit because confusing authentication credentials, package encryption keys, and recovery credentials is a common source of security defects.

Clone this wiki locally