secret

An OCaml library for key material. A Secret.t owns a fixed-length buffer allocated in C memory, so the garbage collector never copies the payload and never leaves a stale copy behind. The bytes are zeroized on destroy, when the handle is collected, and at normal process exit.

Copies of an AES-256 key left in process memory after cleanup
Case Raw key Expanded key
Key held only in Secret.t00
Zero-copy view; schedule died young and the minor heap was scrubbed00
Zero-copy view; schedule reached the major heap01
Heap-string baseline; schedule reached the major heap11

The scanner walks every writable mapping of the process looking for the key. Passing a view instead of a string removes the raw-key copy; it cannot remove a key schedule the receiving library built in the OCaml heap. Method and full output: the leak census.

Experimental and unaudited. The API may change before 1.0. This library reduces the number of places a key can be read from; it is not a substitute for an HSM or a KMS when the threat model needs hardware-backed isolation.

Current state
Release0.1.0
OCaml4.14 and 5.0–5.5
Tested platformsLinux and macOS on every commit; musl and 32-bit weekly; Windows and solo5 best effort
Runtime dependenciesnone beyond the standard library
LicenseISC

Quick start

opam pin add secret https://github.com/thevilledev/ocaml-secret.git

Depend on the library from dune:

(executable
 (name main)
 (libraries secret))

Generate a key, use it, and let the scope destroy it:

let aes =
  Secret.with_random 32 (fun key ->
    Secret.Unsafe.with_string_view key Mirage_crypto.AES.GCM.of_secret)

with_random writes OS entropy straight into the secret memory — no copy passes through the OCaml heap — and destroys the secret when the function returns or raises. with_string_view hands mirage-crypto an ordinary string whose bytes are that memory, so no copy is made on the way in either. Nothing here waits for the collector.

Choose a tier

Default
calloc memory, zeroed on release, with unviewed blocks pooled for reuse. About 75 ns to create and destroy. Use it for many small or short-lived keys.
Hardened
Secret.create ~hardened:true gives the payload its own mapping with guard pages on both sides, a canary, mlock, and exclusion from core dumps. Costs at least three pages of address space and roughly 1.6 µs per secret. Use it for long-lived root keys.

Hardening is disabled by default, and every hardening feature is best effort and platform-dependent. Secret.status reports what a given value actually got, and Secret.require_hardening turns a missing protection into a raised exception rather than a silent downgrade. See what is and is not guaranteed, or start with usage.