secretbox

package
v0.45.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 9 Imported by: 0

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

View Source
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

func IsSealed

func IsSealed(stored string) bool

IsSealed reports whether a stored value was produced by this package. Used by a migration to tell the rows it still has to convert from the ones it has.

Types

type Box

type Box struct {
	// contains filtered or unexported fields
}

Box seals and opens secrets with one key.

func New

func New(key string) (*Box, error)

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) Enabled

func (b *Box) Enabled() bool

Enabled reports whether a key was configured.

func (*Box) Open

func (b *Box) Open(stored string) (string, error)

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.

func (*Box) Seal

func (b *Box) Seal(plaintext string) (string, error)

Seal encrypts a secret for storage. The nonce is random per call and stored with the ciphertext, so sealing the same secret twice gives different bytes — which is what stops the column from revealing that two accounts share a secret.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL