trackslash
VAULT-26 P2

Investigate encrypting the vault on device with the app lock password

0
All issues

Description

The app lock password (VAULT-22) should encrypt the whole vault on the device, not just hide it behind a lock screen. This is only worth doing if it can be done without compromises. Find out whether it can, and write up the design or the reasons it can't.

Today:

  • The vault is a SwiftData store (PersistedLocalVaultStoreFactory), kept in the app group container so the widgets and AutoFill can read it.
  • Nothing in it is encrypted at rest beyond iOS file protection. The exception is items that have their own password (encryptedItemDetails).
  • SwiftData has no built-in store encryption.

Approaches to evaluate:

  • Field-level encryption: encrypt in PersistedVaultItemEncoder and PersistedVaultTagEncoder.
    • Any field that SwiftData predicates search or sort on would need filtering in memory instead.
    • Work out what would still be readable: item count, dates, order, types.
  • Whole-store encryption: keep SwiftData in memory while unlocked, and write an encrypted copy of the store to disk. Check:
    • crash safety, including atomic writes
    • that schema migrations still work
    • the cost of rewriting the whole store on every change
  • A custom SwiftData DataStore: an encrypted storage backend.
  • Anything else worth considering.

What "no compromises" means:

  • What's protected: nothing that identifies an item or its contents can be read at rest while the app is locked. Tag names are included.
  • Crash safety: no path loses data, whether a crash, an interrupted write, a migration, or a password change.
  • Password changes: a random data key, wrapped by a key derived from the password, so changing the password doesn't mean re-encrypting everything.
  • Unlock speed: unlocking takes about a second at most. The backup password's derivation can take minutes, so this needs its own tuning.
  • Background features: killphrases, search passphrases, backups and auto-backup keep working.
  • Duress vault: the duress vault (VAULT-23) fits the design. Both stores should be indistinguishable at rest, and unlocking either should take the same time.

Consequences to spell out:

  • The widgets and AutoFill can't read an encrypted vault while it's locked.
  • Existing stores need migrating into the encrypted format.
  • A forgotten password means the vault can't be recovered, only restored from a backup.

Outcome:

  • Feasible: the design, broken into implementation issues.
  • Not feasible: a written explanation of why. In that case the vault isn't encrypted with the password.

GitHub

0

No branches or pull requests linked.

Comments

1
Bradley

Done in https://github.com/badbundle/vault-app/pull/612 (merged as ee9130b6): docs/on-device-encryption.md.

Verdict: feasible without compromises, but not with SwiftData.

  • Field-level encryption leaves counts, types, dates, order, killphrase flags and the tag graph readable.
  • A custom SwiftData DataStore silently ignores delete(model:where:) and unique upserts.
  • SwiftData in memory with an encrypted file is too slow for large vaults: 1.1 s to load 5,000 items.

The design:

  • File: a single encrypted file of 16 equal-size slots, always present.
  • While unlocked: the vault is held as plain records in an in-memory record store.
  • Keys: each vault has a random data key, wrapped by an Argon2id-derived key.
  • Key derivation: 64 MiB, with passes calibrated on the device to about 0.5 s when the file is created.
  • Saving: every change is written as an atomic, verified replacement of the whole file.
  • Users who never set a password: they keep today's SQLite store.
  • Turning the password off: rewraps the key with a device key.

Measured on an M5 Max:

  • Key derivation: about 170 ms.
  • Loading 1,000 items: about 9 ms.
  • Saving at 1,000 items: about 19 ms.

Decisions:

  • Argon2id, vendored as C.
  • Parameters calibrated on the device.
  • N = 16 slots, L = 10 reserved for duress nesting.
  • No option to exclude the vault from device backups.
  • Restoring widgets and AutoFill after the password is turned off is in scope.

Implementation:

  • VAULT-22: sub-issues VAULT-40 to VAULT-50.
  • VAULT-23: VAULT-51.
  • VAULT-34: VAULT-52.

Side findings, filed separately: VAULT-53 (backup fields), VAULT-54 (keyboard learning) and VAULT-55 (plain-store residue).