Skip to content

Add optional local data encryption with data.bin vault#5

Merged
henricook merged 7 commits into
henricook:mainfrom
spotco:pr/optional-local-vault-encryption
Jul 5, 2026
Merged

Add optional local data encryption with data.bin vault#5
henricook merged 7 commits into
henricook:mainfrom
spotco:pr/optional-local-vault-encryption

Conversation

@spotco

@spotco spotco commented May 31, 2026

Copy link
Copy Markdown
Contributor

Adds optional encryption for local LibreOTP data by migrating plaintext data.json storage into an encrypted data.bin vault.

This includes:

  • AES-GCM encrypted local vault support using PBKDF2-HMAC-SHA256 key derivation
  • Prefer data.bin over data.json when both exist
  • Prompt to migrate existing plaintext local data into the encrypted vault
  • Password dialogs for creating, unlocking, and changing the vault password
  • Loading overlays while encrypted data is being unlocked, encrypted, or re-saved
  • Tests for vault encryption, storage behavior, password dialogs, and state transitions

@spotco

spotco commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

On load with plaintext json, prompts to encrypt local json (into a data.bin).
image

Prompts for password on encrypt.
image

On loading an encrypted vault, prompts for password.
image

(Password prompt)
image

And passwords on visible upon entering correct password!
image

data.bin (as opposed to data.json) is stored at:
C:\Users<USERNAME>\AppData\Roaming\com.henricook\LibreOTP\

@henricook

henricook commented May 31, 2026

Copy link
Copy Markdown
Owner

Thanks @spotco ! Do you mind if I push a couple of minor amendments and then prompt you to take a look? Broadly though, looking good

@spotco

spotco commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

yep, go ahead 👍

Henri Cook and others added 4 commits May 31, 2026 13:58
- Raise vault KDF to 600k PBKDF2-SHA256 iterations (OWASP guidance)
- Reject out-of-range iteration counts on load (unlock DoS guard)
- Distinguish wrong-password from corrupted-vault errors via typed
  error kinds instead of string matching
- Cache the derived key per session and restrict write-verification
  to migrate / password-change, so routine saves skip PBKDF2
- Share PBKDF2/AES-GCM primitives between the vault and 2FAS services
- Fix off-by-one length guard that blocked decrypting empty payloads
- Cancel pending debounced save during migrate / password change
- Reopen the vault prompt on a failed unlock instead of clearing the
  unrelated 2FAS backup credential
- Expand test coverage for unlock, error-kind, AAD-tamper and
  empty-plaintext paths
…lt-encryption

# Conflicts:
#	lib/presentation/pages/dashboard_page.dart
- Remember when the user picks "Not Now" on the encryption migration
  prompt so it is not shown again on every launch
- Store the choice in SharedPreferences and honour it on load
- The manual "Encrypt Local Data" action remains available afterwards
- Make vault saves atomic: swap via .bak rename instead of
  delete-then-rename, roll back on failure, and recover from leftover
  .tmp/.bak files at load time so an interrupted save can no longer
  lose the vault
- Stop trimming passwords; surrounding whitespace is preserved
- Surface unknown load errors (e.g. corrupt data.json) as a plain
  error state instead of an unwinnable password prompt
- Only re-show the password dialog while a password is still required
- Render friendly load-error copy instead of raw exception text and
  hide the password action when the failure is not password-related
- Guard the debounced save against running concurrently with vault
  operations and cancel it before imports
- Remove dead loadData/readPlaintextJsonObject and unused mock code
- Document the data.bin vault in CLAUDE.md
- Add tests: key-based save round-trip, .tmp/.bak crash recovery,
  unknown-error prompt behaviour, whitespace-preserving passwords
henricook added 2 commits July 5, 2026 12:52
- Validate the full envelope (including ciphertext presence and
  length) before adopting a leftover .tmp, so a header-valid but
  broken temp file cannot shadow a recoverable .bak
- Delete leftover .tmp/.bak swap artifacts once the vault unlocks
  successfully; a crashed password change could otherwise leave a
  copy encrypted under the old password on disk indefinitely
- Tests for both behaviours
@henricook

Copy link
Copy Markdown
Owner

Thanks @spotco !

@henricook
henricook merged commit 2726f66 into henricook:main Jul 5, 2026
1 check passed
henricook added a commit that referenced this pull request Jul 5, 2026
…vault

Reconciles the import merge/append feature with the now-merged local
vault (PR #5). Import persistence goes through the vault-aware
_persistCurrentData() instead of a direct plaintext saveData, so
importing while an encrypted vault is active re-encrypts into data.bin
rather than writing decrypted secrets to data.json. The merge is
committed to state then persisted, rolling back the in-memory merge if
the persist fails. Adds a test asserting imports take the encrypted
path when the vault is active.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants