ks — Key Store
A modern, local-first, git-friendly secret manager built on the
age encryption format.
Architecture
- Identity (
identity.age): a single X25519 secret key, encrypted to the user's passphrase with age scrypt mode. Stays local. - Recipients (
store/.age-recipients): a plaintext list ofage1…public keys allowed to decrypt this store. Git-synced with the secrets. - Secrets (
store/<path>.age): each secret is its own recipient-encrypted age file whose plaintext is just text — the first line is the value,key: valuelines are fields.age -d secret.ageis human-readable and interoperable with theage/rageCLIs.
Asymmetry
Encryption needs only the public recipients, so writing secrets never
prompts for a passphrase. Only reading (and rotating recipients) requires
the unlocked [x25519::Identity].
use age::secrecy::SecretString;
use ks::{Config, Secret, Store, crypto};
fn main() -> ks::Result<()> {
let config = Config::load()?;
let pp = SecretString::from("hunter2".to_owned());
let id = crypto::create_identity(&config.identity_path, pp)?;
let store = Store::create(config, &id, &[])?;
store.set("github/token", &Secret::new("ghp_xxx\nuser: alice"))?; // no unlock
let token = store.get("github/token", &id)?;
assert_eq!(token.password(), "ghp_xxx");
Ok(())
}