Skip to content
LumiDeskPublic

About

Local-first, offline Android password manager and TOTP authenticator. No account, no cloud, no INTERNET permission.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

KeyCase

A local-first password manager and TOTP authenticator for Android.

No account. No cloud. No telemetry. One encrypted vault on your device.

CI License: GPL-3.0-or-later Platform Flutter No cloud No telemetry

KeyCase stores logins, notes, and TOTP/HOTP/Steam codes in a single AES-256-GCM container. A key derived from the master password with Argon2id wraps a random 256-bit vault key, and the container is the only place where entry data ever exists on disk. The app has no account system, no sync, and no analytics; the only network request is the optional update check you trigger from Settings → About.

Screenshots

Home, light
Home
Logins, dark
Logins
Tokens, dark
Tokens
Token detail, dark
Token detail
Editor, dark
Editor
Settings, dark
Settings

Features

Area What you get
Vault Create with a master password, unlock, lock now, auto-lock when leaving the app; unlocking returns to the page you were on
Security Argon2id key derivation, AES-256-GCM, FLAG_SECURE on by default, biometric unlock via Android Keystore, clipboard auto-clear
Logins Account, password, multiple URLs, notes, random password generator, copy to clipboard
Tokens TOTP, HOTP (with counter), Steam; add by QR scan or manually (scanned data opens in the editor for review); one shared ticker, per-row countdown ring, next-code preview; optional notes
Lists A–Z grouping, search across names/accounts/URLs/notes, favorites, three sort orders
Icons Bundled Aegis Simple Icons pack, automatic brand matching, searchable picker, custom image icons
Imports Aegis JSON exports (plain or password-encrypted), Google Authenticator migration links, KeyCase backups
Backups Encrypted .kcbak export/import; up to 10 automatic snapshots in a SAF folder you choose
Cleanup Trash with restore and purge, security center for weak/reused/stale passwords
Appearance System/light/dark, five accent presets, custom HSV color, Chinese/English
Updates Manual "check for updates" against GitHub Releases; no background polling

Tech stack

Layer Choice
UI Flutter 3.47.2, Material 3, project design tokens (kc* colors, radii, KcPage / KcCard / KcHeader)
State ChangeNotifier + InheritedNotifier; no Provider/Riverpod/Bloc
Vault format Single KCV1 file; payload schemaVersion 2: gzip JSON with mutually exclusive login / totp objects, AES-256-GCM, 16-byte tag
Key derivation Argon2id (64 MiB, 3 iterations, p=2) via cryptography; parameters stored in the container header
Native bridge One MethodChannel: FLAG_SECURE, Android Keystore-wrapped vault key, SAF backup folder I/O
Biometrics local_auth / BiometricPrompt with master-password fallback
QR scanning mobile_scanner
Icons flutter_svg + bundled Aegis Simple Icons pack
Preferences shared_preferences (UI topics only; never vault data)
Backup I/O file_selector + Storage Access Framework (.kcbak)
CI/CD GitHub Actions, Flutter 3.47.2, signed arm64 release APK

Architecture

lib/
├── main.dart                    app entry, controller bootstrap, root routing
├── app/
│   ├── keycase_controller*.dart  single source of truth (ChangeNotifier)
│   ├── keycase_scope.dart        InheritedNotifier injection
│   ├── keycase_state.dart        UI state, error/notice codes
│   ├── vault/                    KCV1 container, repository, backups, queries
│   ├── crypto/                   Argon2id, AES-GCM, Base32, secure random
│   ├── otp/                      TOTP/HOTP/Steam and the app-wide clock
│   ├── import/                   otpauth, Aegis, Google migration
│   ├── icons/                    Aegis icon pack and SVG rendering
│   ├── security/                 weak/reused/stale password audit
│   ├── platform/                 native bridge and biometrics
│   ├── models/                   domain models and JSON mapping
│   ├── data/                     device preferences
│   ├── update/                   optional GitHub release check
│   ├── util/                     date, URL, and password helpers
│   ├── logging/                  AppLogger
│   └── app_theme.dart            design tokens and ThemeData
├── pages/                        screens; shared modal helpers live in sheets.dart
├── common_widgets.dart           shared widgets (parts under app/)
└── l10n/                         Chinese/English ARB files and generated code
android/                          Flutter host and the single native bridge
assets/iconpacks/                 Aegis Simple Icons (SVG + pack.json)
docs/                             design system, components, architecture, decisions

Data flow

  1. KeyCaseController.bootstrap() reads preferences and checks for vault.kcv.
  2. VaultRepository derives and verifies keys in a background isolate, then returns an in-memory UnlockedVault.
  3. UI actions call controller methods; the controller builds the next VaultData, VaultRepository.save() serializes writes, and the container is replaced atomically (temp file + rename).
  4. Locking clears the vault key and the decrypted data; the unlock screen covers the current page until you unlock again, then returns you to it. The file stays encrypted.

Security model

  • Encrypted at rest. Entries only exist inside the KCV1 container. The schemaVersion 2 payload is gzip-compressed JSON with mutually exclusive login/token objects, encrypted with a random 256-bit vault key; the vault key is wrapped by a password key derived with Argon2id. The vault id is bound as AAD.
  • No plaintext on disk. Master password, vault key, TOTP secrets, and entry contents are never written to logs, preferences, caches, or temporary files.
  • Clipboard. Copied passwords and codes are cleared after 60 seconds when the clipboard still contains the value KeyCase wrote. This can be disabled in Settings.
  • Biometric unlock. The vault key is encrypted by a non-exportable Android Keystore key and stored in app-private preferences. The system BiometricPrompt gates every unlock; the master password remains the fallback.
  • No cloud backup. allowBackup="false" keeps Android from copying the vault. Backups are encrypted container copies, exported through the system document picker.
  • No telemetry. The only network call is a user-triggered request to api.github.com for the latest release. Nothing is uploaded.
  • No recovery. A forgotten master password cannot be recovered; export a backup first. See SECURITY.md for the threat model.

Build

Requirements: Flutter 3.47.2, Java 17, Android SDK (API 26+).

flutter pub get
flutter run -d emulator-5554
flutter build apk --debug
flutter build apk --release    # signed with signing/keycase-release.jks

Run the CI checks locally:

dart format --output=none --set-exit-if-changed .
flutter analyze
flutter test
flutter build apk --debug

For local UI checks, seed a demo vault and auto-unlock it:

flutter run -d emulator-5554 --dart-define=SEED_DEMO=true

Release

Releases are built by GitHub Actions from a vX.Y.Z tag:

dart run scripts/publish.dart patch   # or minor / major

The script bumps pubspec.yaml and lib/app/app_version.dart, runs format/analyze/test, commits, tags, and pushes. CI builds a signed arm64 APK and creates a prerelease. Promote it to latest after installing it on a device.

Project status

Current release: 1.1.0.

Only Android is supported. The vault payload is schemaVersion 2. The android/ directory is the sole delivery target; there is no web or desktop build. See CHANGELOG.md for user-visible changes.

Documentation

License

GPL-3.0-or-later. See LICENSE.

About

Local-first, offline Android password manager and TOTP authenticator. No account, no cloud, no INTERNET permission.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages