Documentation
¶
Overview ¶
Package secretbox encrypts the secrets filex must be able to READ BACK.
Why this exists at all ¶
Almost every credential filex stores is one-way hashed: account passwords are bcrypt, API tokens are sha256 (apitoken.go:153). Verification only ever needs to compare, so the plaintext is never needed again and never kept.
The protocol gateway breaks that. SigV4 does not send the secret — it sends an HMAC chain computed FROM the secret, so the server must derive the same chain, which means holding the secret in recoverable form. NTLMv2 is the same shape for a different reason (it needs MD4 of the password). No amount of code makes a one-way hash verify either of them.
So the choice is not "hash or encrypt". It is "plaintext in a column, or encrypted with a key that lives somewhere else", and the owner chose the second (2026-08-16).
What it actually buys, stated honestly ¶
One thing: a leaked DATABASE is not a leaked set of secrets. That is not hypothetical here — the database travels, by design, into Backrest snapshots, an S3 mirror and a DR restore on another machine, and this month a stray 15 GB file proved how far those copies reach. The key is supplied through the environment and stays on the host.
It buys nothing at all against an attacker who already has the host, since the process must be able to decrypt. Anyone treating this as more than "backups no longer carry live credentials" is mis-reading it.
Deliberately NOT included ¶
The existing `storages.config.secret_key` values are left in plaintext for now (the owner picked the narrow option). That inconsistency is recorded rather than hidden: this package is the mechanism, and pointing the storage configs at it later is a migration, not a redesign.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoKey means the box was built without a key. Encryption fails loudly // rather than silently storing plaintext — a "temporarily unencrypted" // secret is one nobody ever goes back for. ErrNoKey = errors.New("secretbox: no encryption key configured") // ErrCorrupt covers a ciphertext that is malformed, truncated, or was // sealed under a different key. They are one error on purpose: the caller's // action ("this credential can no longer be used — issue a new one") is the // same, and distinguishing them would leak whether a guess got the format // right. ErrCorrupt = errors.New("secretbox: ciphertext cannot be opened") )
Errors callers distinguish. Everything else is a programming mistake.
Functions ¶
Types ¶
type Box ¶
type Box struct {
// contains filtered or unexported fields
}
Box seals and opens secrets with one key.
func New ¶
New derives a Box from the configured key material.
The key is hashed to 32 bytes rather than required to BE 32 bytes, so an operator can use a passphrase from a password manager instead of hunting for a base64 generator. sha256 is enough here: the input is a high-entropy secret from a config file, not a human-chosen password being stretched.
An empty key returns a usable Box that refuses to Seal (ErrNoKey) and passes plaintext through Open unchanged, so an install that has not configured a key keeps working for everything that does not need one.
func (*Box) Open ¶
Open decrypts a stored value.
A value without the prefix is returned as-is. That is what lets a column hold legacy plaintext next to encrypted rows during a migration, and it is safe precisely because the prefix is explicit: nothing is ever guessed.