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

crypto

package
v1.10.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package crypto provides AES-256-GCM encryption for GDPR crypto-erasure.

Index

Constants

View Source
const ErasedMarker = "[ERASED]"

ErasedMarker replaces a sealed value whose key has been destroyed.

Variables

View Source
var ErrAlreadySealed = errors.New("crypto: event is already sealed")

ErrAlreadySealed is returned when sealing an event that already carries ciphertext, which would make the original unrecoverable behind two layers.

View Source
var ErrKeyScopeMismatch = errors.New("crypto: event's key ID names a different scope")

ErrKeyScopeMismatch is returned by Sealer.Open when an event's recorded key ID names a different app, tenant or subject than the event itself. EncryptionKeyID is not covered by the hash, so this is how an edited row is caught before it can point a read at another scope's key.

Functions

func IsSealed

func IsSealed(event *audit.Event) bool

IsSealed reports whether any of the event's payload fields hold ciphertext.

func ParseScopedKeyID

func ParseScopedKeyID(keyID string) (appID, tenantID, subjectID string, ok bool)

ParseScopedKeyID reverses ScopedKeyID. ok is false for any string ScopedKeyID could not have produced, which includes every legacy key ID: the bare subject IDs that events were sealed under before keys were scoped.

func ScopedKeyID

func ScopedKeyID(appID, tenantID, subjectID string) string

ScopedKeyID returns the key store identifier for a subject's key within one app and tenant. Sealer.Seal asks the key store for this ID and records it in Event.EncryptionKeyID, and erasure destroys it.

The scope is part of the identity because subject IDs are not unique across apps and tenants. "user-42" in one tenant is usually a different person from "user-42" in the next, and when the key was addressed by subject alone, erasing one of them destroyed the other's data too.

Each field is length-prefixed ("<byte-length>:<value>"), the same way checkpoint.CanonicalPayload renders its fields. AppID and TenantID are free text, so a bare join lets a separator inside one value move the boundary: joined with ":", app "a:b" with tenant "c" and app "a" with tenant "b:c" would name the same key. With a length in front of each field, a reader finds where it ends without trusting what is inside it.

The result contains ":" and "|" and whatever bytes the IDs themselves hold. A key store that maps IDs onto paths or resource names has to escape or hash them, and treat the ID as opaque.

Types

type Encryptor

type Encryptor struct{}

Encryptor provides AES-256-GCM envelope encryption.

func NewEncryptor

func NewEncryptor() *Encryptor

NewEncryptor creates a new Encryptor.

func (*Encryptor) Decrypt

func (e *Encryptor) Decrypt(key, ciphertext []byte) ([]byte, error)

Decrypt decrypts ciphertext (nonce + encrypted data) using AES-256-GCM. The key must be 32 bytes (256 bits).

func (*Encryptor) Encrypt

func (e *Encryptor) Encrypt(key, plaintext []byte) ([]byte, error)

Encrypt encrypts plaintext using AES-256-GCM with the given key. The key must be 32 bytes (256 bits). Returns nonce + ciphertext (nonce is prepended).

type InMemoryKeyStore

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

InMemoryKeyStore is an in-memory key store for testing.

func NewInMemoryKeyStore

func NewInMemoryKeyStore() *InMemoryKeyStore

NewInMemoryKeyStore creates a new InMemoryKeyStore.

func (*InMemoryKeyStore) Delete

func (ks *InMemoryKeyStore) Delete(keyID string) error

func (*InMemoryKeyStore) Get

func (ks *InMemoryKeyStore) Get(keyID string) ([]byte, error)

func (*InMemoryKeyStore) GetOrCreate

func (ks *InMemoryKeyStore) GetOrCreate(keyID string) (retKey []byte, retKeyID string, retErr error)

type KeyStore

type KeyStore interface {
	// GetOrCreate returns the key stored under keyID, creating one if none
	// exists. The returned ID is informational: Sealer records keyID itself,
	// because keyID is what a later Get has to ask for.
	GetOrCreate(keyID string) ([]byte, string, error)

	// Get returns the key stored under keyID, or ErrErasureKeyNotFound.
	Get(keyID string) ([]byte, error)

	// Delete destroys the key stored under keyID, making everything sealed
	// with it irrecoverable. Deleting a key that does not exist is not an error.
	Delete(keyID string) error
}

KeyStore holds the encryption keys crypto-erasure destroys.

Every method takes an opaque key ID. Sealer builds it with ScopedKeyID, so each (app, tenant, subject) gets its own key and erasing a subject in one scope cannot touch another. Implementations must store and compare the ID as given, byte for byte, and must not parse it or assume it is a subject ID.

Events sealed before keys were scoped used the bare subject ID as the key ID. Those keys live in the same store under that name, and Sealer.Open still asks for them by it, so an existing durable key store keeps working across the upgrade without moving a single key. See the erasure package for what erasing such a key does when more than one scope depends on it.

The ID space is shared between the two forms. That is safe because a scoped ID always starts with "ck1|" followed by a length, and nothing new is ever sealed under a legacy ID.

type Sealer

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

Sealer encrypts and decrypts the personal payload of an audit event using a key per subject within each app and tenant (see ScopedKeyID). That is what makes crypto-erasure work: destroy the key and the payload is unrecoverable while the event's shape and hash chain survive.

Sealed fields are Metadata, Reason and IP. Deliberately left in plaintext:

  • SubjectID, because it is the lookup key used to find a subject's events in order to erase them, and to prove afterwards that they were erased.
  • UserID, because it identifies the actor rather than the data subject, and because the per-user index and ByUser lookups depend on it.
  • Action, Resource, Category, Outcome, Severity, ResourceID and the timestamps, because they are the operational record that must outlive an erasure, and queries and compliance reports group on them.

So an erasure destroys what was recorded about a subject, not the fact that an event involving them occurred. That is the intended posture for an audit log, and callers should not read more into it than that.

func NewSealer

func NewSealer(keys KeyStore) *Sealer

NewSealer creates a Sealer backed by the given key store.

func (*Sealer) Open

func (s *Sealer) Open(event *audit.Event) error

Open decrypts a sealed event in place.

When the subject's key has been destroyed, the sealed fields are replaced with ErasedMarker and the event is flagged Erased, rather than returning an error: a destroyed key is the expected end state of an erasure, not a failure, and the remaining record still has to be readable.

An event already flagged Erased is redacted the same way without touching the key. Its key can outlive the erasure when a legacy key is shared with another scope (see the erasure package), and an erased event must never display its payload whether or not the key is still around.

Open must not be used on events destined for hash verification. The stored bytes are what the digest covers, so a verifier has to see the sealed form.

func (*Sealer) OpenAll

func (s *Sealer) OpenAll(events []*audit.Event) error

OpenAll decrypts each event in place, stopping at the first hard failure.

func (*Sealer) Seal

func (s *Sealer) Seal(event *audit.Event) error

Seal encrypts the event's personal payload in place and records which key was used.

It is a no-op for an event with no SubjectID: there is no subject to key the encryption to, and a key that is never destroyed protects nothing.

Callers must Seal before computing the event's hash. Hashing the plaintext would make the chain unverifiable the moment a key is destroyed, because the original bytes needed to recompute the digest would be gone.

Jump to

Keyboard shortcuts

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