Help us shape the future of Go! Take the 2026 Go Developer Survey

crypto

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package crypto provides at-rest AES-256-GCM encryption under a VERSIONED KEYRING, so a key can be rotated without making every already-sealed value permanently unreadable.

The wire format is the "CPK1" envelope, byte-for-byte compatible with the one control-plane has shipped; blobs sealed by either implementation open with the other. See envelope.go for the layout and the reasoning for it.

There is no process-global state and no hard-coded environment variable: build a *Keyring with ParseKeyring or KeyringFromEnv and pass it to whoever seals or opens.

Rotation

The keyring is an ordered list; the FIRST entry is the primary and the only key that seals, while every entry can open. Rotating is one prepend:

before: v1:AAA...
after:  v2:BBB...,v1:AAA...

New writes use v2, old rows still open under v1. Keep v1 until a backfill has re-sealed every row (EnvelopePrefixForKeyID builds the SQL prefix for "which rows still use v1"), then delete it. At no point is a key referenced that is not present, so neither step has an ordering hazard.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrKeyNotConfigured: the keyring source (env var) is unset or blank.
	// Whether that is fatal is the caller's call; an absent keyring is not
	// the same as a malformed one.
	ErrKeyNotConfigured = errors.New("encryption keyring not configured")

	// ErrKeyringInvalid: present but malformed — bad entry, bad key id, key
	// not 32 bytes, duplicate id, or a corrupt envelope header. Fail at boot.
	ErrKeyringInvalid = errors.New("not a valid encryption keyring")

	// ErrUnknownKeyID: a well-formed blob naming a key the keyring does not
	// hold. This means "put the key back", not "someone tampered with it".
	ErrUnknownKeyID = errors.New("ciphertext was sealed under a key id that is not in the keyring")

	// ErrUnversionedCiphertext: no envelope header, i.e. the pre-keyring raw
	// nonce ‖ ciphertext format. Refused rather than opened under an
	// implicit "v1"; see decodeEnvelope.
	ErrUnversionedCiphertext = errors.New("ciphertext predates the versioned envelope and cannot be opened")
)

Sentinels are distinguishable with errors.Is. An operator who rotates a key must be told what they did, not shown a bare authentication failure.

Functions

func EnvelopePrefixForKeyID

func EnvelopePrefixForKeyID(keyID string) ([]byte, error)

EnvelopePrefixForKeyID returns the exact leading bytes of every blob sealed under keyID — the SQL prefix for "which rows still use this key".

Types

type Keyring

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

Keyring is a set of AES-256 keys: every one can open, exactly one (the primary) seals. Safe for concurrent use after construction.

func KeyringFromEnv

func KeyringFromEnv(name string, getenv func(string) string) (*Keyring, error)

KeyringFromEnv parses the keyring held in the variable name, read through getenv — pass os.Getenv. forge/pkg never reads the ambient environment itself (a library changes behaviour only through its arguments), so the caller owns where the value comes from. An unset or blank variable yields ErrKeyNotConfigured (wrapped with the name); a malformed one yields ErrKeyringInvalid. Callers that accept "absent" but treat "malformed" as fatal can branch on errors.Is.

func ParseKeyring

func ParseKeyring(raw string) (*Keyring, error)

ParseKeyring reads the keyring grammar:

keyring = entry [ "," entry ]*
entry   = key-id ":" base64(32 bytes)
        | base64(32 bytes)          -- single entry only; id becomes "v1"

The first entry is the primary. Position rather than a second "primary=" name was chosen because two settings can disagree in the worst way: a primary naming a key absent from the keyring boots fine and opens every old row, then fails on the first write. With position, you cannot prepend a key without adding it. Whitespace around entries and ids is tolerated.

func (*Keyring) KeyIDs

func (k *Keyring) KeyIDs() []string

KeyIDs returns every id available for opening, primary first.

func (*Keyring) Open

func (k *Keyring) Open(ciphertext, aad []byte) ([]byte, error)

Open decrypts a blob, selecting the key its envelope names. Failure modes are distinct sentinels:

ErrUnversionedCiphertext — sealed before the envelope existed
ErrUnknownKeyID          — a key that was removed from the keyring
ErrKeyringInvalid        — corrupt envelope header
(wrapped GCM failure)    — tampering or wrong aad; the only security event

Collapsing a rotation mistake into an authentication failure sends the operator on an intrusion hunt when the answer is "the key you removed is still in use".

func (*Keyring) PrimaryKeyID

func (k *Keyring) PrimaryKeyID() string

PrimaryKeyID returns the id new writes seal under.

func (*Keyring) Seal

func (k *Keyring) Seal(plaintext, aad []byte) ([]byte, error)

Seal encrypts plaintext under the PRIMARY key and cryptographically binds it to aad and to its own envelope header.

aad is not encrypted and not stored: the opener re-derives it from the context it believes the blob belongs to, and Open with different aad fails authentication. That makes a sealed value NON-TRANSPLANTABLE, a stronger claim than any access check — a WHERE clause does nothing if the ciphertext is copied into a row the wrong caller legitimately owns. nil aad is the unbound case. A blob sealed with aad does not open with nil aad.

New writes always use the primary, with no per-call override: a caller that could pick its key could keep writing under a key a rotation is retiring, and the backfill would never finish.

Jump to

Keyboard shortcuts

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