go-secretbox is the boring, correct version of the encryption code everyone ends up writing once: stretch a password with a slow KDF, encrypt with an authenticated cipher, prepend the nonce, store a salt, verify the password without storing it. It gets the parts that are easy to get wrong — nonce generation, constant-time checks, key zeroing, self-describing formats — out of your codebase.
It was extracted from matcha's "secure mode," which encrypts a mail client's config and caches behind a master password.
- Two layers, one set of primitives.
Seal/Unseal— one-shot, self-describing blobs. The salt and KDF parameters travel inside the ciphertext, so a blob is decryptable years later with only the password.Vault— the long-lived "secure mode" pattern: a metadata file with a salt + encrypted sentinel, an in-memory session key, transparent file encryption, password change, and key rotation.
- Sentinel password verification. No password, and no hash of it, is ever stored.
Unlockdecrypts a known sentinel and compares in constant time. - Pluggable KDF and cipher. Argon2id + AES-256-GCM by default; swap in XChaCha20-Poly1305 (or your own
KDF/Cipher) via options. The choice is recorded in metadata, soUnlock/Unsealalways reconstruct the right algorithm. - Key hygiene. Derived keys are zeroed after use and on
Lock.Rekeydecrypts-all-then-rotates so a failure can't leave files stranded. - Small surface, single dependency. Just
golang.org/x/crypto.
go get github.com/floatpane/go-secretboxRequires Go 1.26+.
package main
import (
"fmt"
"log"
"github.com/floatpane/go-secretbox"
)
func main() {
blob, err := secretbox.Seal([]byte("attack at dawn"), "correct horse battery staple")
if err != nil {
log.Fatal(err)
}
// blob is safe to write to disk — it carries its own salt + KDF params.
plain, err := secretbox.Unseal(blob, "correct horse battery staple")
if err != nil {
log.Fatal(err) // ErrDecrypt on wrong password or tampering
}
fmt.Println(string(plain)) // attack at dawn
}v := secretbox.NewVault("/home/me/.config/app/secure.meta")
// First run — turn secure mode on.
if !v.Initialized() {
if err := v.Init(masterPassword); err != nil {
log.Fatal(err)
}
}
// Later runs — unlock with the master password.
if err := v.Unlock(masterPassword); err != nil {
log.Fatal(err) // ErrWrongPassword
}
defer v.Lock() // zeroes the session key
// Transparent file encryption while unlocked.
v.WriteFile("/home/me/.config/app/config.json", configBytes, 0o600)
data, _ := v.ReadFile("/home/me/.config/app/config.json")// Decrypts every file with the old key, rotates the vault, re-encrypts with
// the new key. Phase-ordered so a crash can't strand your data.
err := v.Rekey(newPassword, []string{
"/home/me/.config/app/config.json",
"/home/me/.config/app/cache.db",
})v := secretbox.NewVault(metaPath,
secretbox.WithCipher(secretbox.ChaCha20Poly1305{}),
secretbox.WithKDF(secretbox.NewArgon2id(secretbox.Argon2idParams{
Time: 4, Memory: 128 * 1024, Threads: 4,
})),
)| Knob | Default | Notes |
|---|---|---|
| KDF | Argon2id | Time=3, Memory=64 MiB, Threads=4 (interactive-login baseline) |
| Cipher | AES-256-GCM | 32-byte key, 12-byte random nonce, prepended to ciphertext |
| Salt | 16 random bytes | fresh per Init/Seal/Rekey |
| Sentinel | secretbox-verified |
encrypted under the key, compared constant-time on Unlock |
- Not key management. It protects data with a password. If the password leaks, so does the data.
- Not memory-hardened against root. While unlocked, the key lives in process memory. A privileged local attacker (or a core dump) can read it.
Lockshortens that window; it does not close it against an attacker withptrace. - Not a replacement for an OS keyring. It's complementary — matcha uses the keyring when secure mode is off and a
Vaultwhen it's on.
Full API reference: pkg.go.dev/github.com/floatpane/go-secretbox
Guides and diagrams: see docs/.
| Project | Role |
|---|---|
| floatpane/matcha | Reference consumer — uses this library for its config/cache "secure mode." |
| floatpane/go-uds-jsonrpc | Sibling extraction — local daemon JSON-RPC over Unix sockets. |
PRs welcome. See CONTRIBUTING.md.
Report vulnerabilities privately via SECURITY.md.
MIT. See LICENSE.