keybay
One encrypted, platform-protected local store for each Dart or Flutter host application. No Flutter plugin, account, daemon, or network service.
Requires Dart 3.11 or later, including when used through Flutter.
Desktop Dart builds also run Keypass's native build hook. The build machine needs CMake, a C++17 compiler, Python 3, pkg-config, libfido2 1.16+, OpenSSL 3 and libcbor development files (plus patchelf/binutils on Linux), including for applications that use only passphrase protection. See Keypass's build prerequisites. Distributors bundle the native libraries; end users do not install build tools. Mobile and system-passkey app hosts have separate native setup requirements in the SDK guide.
See the release readiness record for the SDK's qualification scope and the separate CLI distribution gates.
Version 0.2.0 replaces the 0.1.x API and encrypted format. It does not read, migrate or delete existing V1 stores. An upgrade does not carry those secrets into a V2 store; applications needing the old data must handle that transition before adopting this version.
import 'package:keybay/keybay.dart';
final session = await Keybay.open();
try {
await session.set('api-token', 's3cr3t');
final token = await session.get('api-token');
await session.delete('api-token');
} finally {
await session.close();
}
Strings are the default. getBytes, setBytes, and getManyBytes support
binary or bounded batch access. listKeys returns authenticated names without
decrypting record values. clearAll removes records while preserving store
protection; selector-free Keybay.reset() removes the current application's
encrypted store, staging, and deletable provider state. It retains nonsecret
coordination locks; the next successful open generates a fresh store key.
A retained platform root without its complete encrypted file returns
storeStateConflict, including after some interrupted initializations or Apple
reinstalls/restores. Follow the deliberate recovery guidance;
do not automatically reset on error.
Opening, changing authentication, and resetting may invoke trusted OS/provider
UI. Record operations and auth.list() never prompt. Mandatory platform
protection cannot be bypassed. Hardware credentials accept optional PIN bytes;
the application handles typed errors when a PIN or an unambiguous connection is
required. Credential objects do not contain UI callbacks.
Application identity
The production API accepts no application ID, path, platform-protector override,
or store name.
iOS, Android, and entitled macOS builds use OS-authenticated application facts.
An ordinary Dart executable on Linux or unentitled macOS declares a stable
namespace in its owning pubspec.yaml:
keybay:
application_id: com.example.my_app
For AOT output, use dart run keybay:keybay_compile bin/app.dart -o app so the
declaration is embedded. A declared desktop namespace prevents accidental
collisions but is not an OS-enforced authorization boundary.
Add --aot-snapshot before the entrypoint when building a separate native AOT
module. Hardened macOS distribution requires signing the module and its
dedicated Dart AOT runtime with the same Developer ID team. The single-file
Dart executable has a separately recorded hardened-runtime startup limitation.
Additional passphrase protection
import 'dart:convert';
import 'dart:typed_data';
final phrase = Uint8List.fromList(utf8.encode(userPassphrase));
try {
await session.auth.add(PassphraseCredential(phrase: phrase));
} finally {
phrase.fillRange(0, phrase.length, 0);
}
After enrollment, Keybay.open() returns authRequired; reopen with a
PassphraseCredential. Closing the session clears Keybay's in-memory store-key
buffer. The encrypted file never becomes plaintext.
A store supports zero or one passphrase and multiple passkey methods (eight
total methods maximum). Adding a second passphrase throws
authMethodAlreadyConfigured; remove the existing method before adding another. These are
alternative unlock methods on top of the mandatory platform root; configuring
both does not require the user to present both.
Additional passkey protection
Use the same credential API for OS-provider passkeys and physical FIDO2 keys. Configure the RP once and enroll explicitly on a new or platform-only store:
const systemPasskey = PasskeyCredential.system(
rpId: 'vault.example.com',
);
final session = await Keybay.open();
try {
await session.auth.add(systemPasskey, label: 'Personal vault');
await session.set('api-token', 's3cr3t');
} finally {
await session.close();
}
Later, Keybay.open(credential: systemPasskey) authenticates using the saved
method. Credential-based open never enrolls protection, for either passphrases
or passkeys. If the encrypted file is missing it fails with storeNotFound
without creating a root or invoking the passkey provider. Await enrollment
success before writing records that should require the added protection.
To add a passkey to an existing store, first open it using its current
protection, then call session.auth.add:
final method = await session.auth.add(
PasskeyCredential.hardware(
rpId: 'dev.example.vault',
pin: pinBytes,
),
label: 'Backup key',
);
final methods = await session.auth.list();
await session.auth.remove(method);
The optional hardware PIN uses caller-owned UTF-8 bytes. Operations copy it synchronously and clear their copy; clear your own bytes after submitting the call. Credential objects contain data, not UI callbacks. A sole connection is selected automatically; ambiguous hardware discovery fails explicitly.
Auth management is add, list, and remove only. Removal accepts the method
object returned by add or list and checks that it belongs to this vault.
Changing a passphrase means removing it and adding a new one. These are two
commits: removing the last method leaves platform-only protection, including
if the subsequent add fails. For passkeys, add the new key before removing the
old one when capacity allows. Each add gets a fresh method ID.
To reopen, use Keybay.open(credential: credential, methodId: method.id).
Omit methodId when exactly one enrollment matches the credential. Passkeys
are scoped by explicit RP ID; provider setup remains the app's responsibility.
The system route needs an appropriate native app host and platform domain
associations. Standalone CLIs use hardware. Keybay adds no hosted service or
automatic browser fallback. Removing an enrollment does not delete its passkey
from the provider. The mandatory platform root remains required; a synced
passkey alone does not make a vault file portable.
See the SDK guide, security policy, and V2 RFC.
Supported production profiles are iOS, Android 12+, macOS, and ordinary Linux desktop. The Flatpak candidate uses sandbox identity, private ciphertext, and XDG Secret Portal protection. Two-app isolation has passed for recorded native Linux and nested Docker configurations; see the qualification report for source applicability and remaining gates. Reset retains the portal-owned application secret, so an older complete encrypted backup can restore access. Flatpak never falls back to ordinary Secret Service. Windows, Snap, and unsupported provider configurations fail closed. MIT licensed.
The scoped pre-1.0 release retains platform CI and recorded physical baseline, upgrade and crash evidence. Remaining lock/reboot, auth-interruption and actual backup/restore/transfer qualification is deferred. Maintained-device Argon2 latency/memory acceptance is lower priority; no accepted performance budget is claimed. These limits and the recorded provider/device configurations are part of the release scope, not passing results for unobserved behavior.
Libraries
- keybay
- One encrypted, platform-protected store for the current host application.