microkv

June 17, 2026 ยท View on GitHub

Actions crates.io version Docs

microkv is a small key-value store for sensitive in-memory data, aiming to miminize its exposure to local attackers who can snoop on your application's virtual memory.

Features

  • Key material held in memory-locked, auto-zeroed storage (memsec).
  • Entries are encrypted with ChaCha20-Poly1305 under a key derived via scrypt / argon2 / raw keys.
  • Anti-tampering through authenticated file header when persisted to disk.
  • Other database features: isolated namespaces ("trees"), atomic operations, rollback-on-error transactions, password rotation, and TTl/expiry.

Anti-features

  • No plaintext mode โ€” a credential is mandatory.
  • No command line interface, server, or networking.
  • Does not defend against an attacker with full kernel page-table read/write.

Usage

In your Cargo.toml:

[dependencies]
microkv = "0.3.0"

Basic usage

use microkv::{MicroKV, Credential};

// open (or create) an encrypted store on disk
let db = MicroKV::open("store.kv", Credential::password("p@ssw0rd"))?;

db.put("name", &"test".to_string())?;          // any Serialize value
let name: String = db.require("name")?;         // errors if absent
let maybe: Option<u32> = db.get("count")?;      // None if absent
db.remove("name")?;

db.save()?;                                     // flush to disk

MicroKV derefs to its default namespace, so db.put(..) and db.namespace("").put(..) are the same thing.

In-memory store

use microkv::{MicroKV, Credential};

// ephemeral store, never touches disk
let cache = MicroKV::in_memory(Credential::password("p@ssw0rd"))?;

Using *_with methods, we can also pass a Config for customizations:

use microkv::{MicroKV, Credential, Config, AutoSave, LockMode, KdfParams};

let db = MicroKV::open_with(
    "store.kv",
    Credential::password("p@ssw0rd"),
    Config {
        autosave: AutoSave::OnEveryWrite,   // persist after each write
        lock_mode: LockMode::Exclusive,     // cross-process file lock
        kdf: KdfParams::sensitive(),        // stronger KDF for new stores
        ..Default::default()
    },
)?;

// open read-only: writes return Error::ReadOnly
let ro = MicroKV::open_with(
    "store.kv",
    Credential::password("p@ssw0rd"),
    Config { read_only: true, ..Default::default() },
)?;

There are also create_new / open_existing (and their *_with variants) when you want to fail instead of silently creating or opening.

Namespacing

let users = db.namespace("users");
let sessions = db.namespace("sessions");

users.put("alice", &42u32)?;
sessions.put("alice", &"token-xyz".to_string())?;   // same key, no collision

let id: Option<u32> = users.get("alice")?;

Atomic updates

// read-modify-write under one lock (no get/put race)
db.update::<u32, _>("counter", |cur| Some(cur.unwrap_or(0) + 1))?;

// insert only if missing
let v: u32 = db.get_or_insert_with("seed", || 7)?;

// compare-and-swap
let swapped = db.compare_and_swap("counter", Some(&1u32), Some(&2u32))?;

Transactions

All operations apply together; returning Err rolls everything back. Namespaces are addressed explicitly ("" is the default).

db.transaction(|tx| {
    let balance: u64 = tx.require("", "balance")?;
    tx.put("", "balance", &(balance - 10))?;
    tx.put("audit", "last", &"debit".to_string())?;
    Ok(())
})?;

Expiring entries (TTL)

use std::time::Duration;

db.put_with_ttl("otp", &"123456".to_string(), Duration::from_secs(60))?;
let purged = db.sweep_expired()?;   // drop expired entries

Iteration

let all_keys: Vec<String> = db.keys()?;
let sorted: Vec<String> = db.keys_sorted()?;

// decrypt every entry matching a prefix
let active: Vec<(String, u32)> = db.namespace("users").prefix("admin:")?;

Password rotation

// verify the old password, then re-encrypt everything under the new one
db.change_password("p@ssw0rd", "even-better-passphrase")?;

License

MIT license