crypto

package
v0.9.9 Latest Latest
Warning

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

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

Documentation

Overview

Package crypto encrypts message bodies.

It wraps another codec: the payload is encoded as usual and the bytes are then encrypted, so the broker, its disk, its backups and anybody reading its management interface see ciphertext.

keyring, err := crypto.NewKeyring(crypto.Key{ID: "2026-01", Secret: secret})
codec := crypto.Wrap(acemq.JSONCodec{}, keyring)
mq, err := acemq.Connect(ctx, url, acemq.WithCodec(codec))

Reading and writing with the other libraries

The framing is the family's, byte for byte — Java, .NET, Python and Ruby all write it — so a producer in any of them and a Go consumer can share a key. See ContentType for the bytes.

.NET wrote AES-256-CBC with a separate HMAC up to its own 0.3.0 and moved to AES-GCM in the same round this library moved its framing, so it is no longer the exception it was. Its old bodies are read only by .NET.

What this does not protect

Headers travel in the clear. The envelope — identity, type, correlation, causation — is how the library routes and retries, so it cannot be encrypted without the broker losing the ability to do its job. Do not put anything secret in a header.

It also does not authenticate the sender. Anybody holding the key can write a message this codec will happily decrypt, so a key is a shared secret between everyone who may publish and everyone who may read, and nothing more.

Nothing outside the standard library is needed.

Index

Constants

View Source
const ContentType = "application/vnd.acemq.encrypted"

ContentType is what an encrypted message carries. Java, .NET, Python and Ruby write the same string.

A message is framed as

[0xAE][1 byte version][1 byte key id length][key id][12 byte nonce][ciphertext+tag]

which is byte for byte what Java, .NET, Python and Ruby write, so a body encrypted here opens there and theirs opens here, given the same key. .NET used AES-256-CBC with a separate HMAC-SHA-256 up to its own 0.3.0 and is no longer the exception it was.

The magic byte is the reason for the shape. Without it the first byte of a body is a version number small enough to be the first byte of a protobuf or an Avro record, and a consumer pointed at a plaintext queue would try to decrypt it and report the failure as a decryption problem. 0xAE is not a plausible first byte of anything else this library writes, so a body that does not start with it is refused as what it is.

View Source
const KeySize = 32

KeySize is the length of the key NewKey draws. AES-256.

It is not the only length accepted: see Key.Secret.

View Source
const MaxKeyIDBytes = 255

MaxKeyIDBytes is the longest a key id may be, in UTF-8 bytes.

It is one byte of framing in front of every message, so it is a name rather than a description.

Variables

This section is empty.

Functions

func KeyIDOf

func KeyIDOf(body []byte) (string, error)

KeyIDOf reads which key a message was encrypted with, without decrypting it.

For working out why a message will not open, and for a tool that has to route messages to whoever holds the key. It reads the framing Codec.Decode reads and refuses, with the same error, everything Codec.Decode refuses.

Types

type Codec

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

Codec encrypts what another codec produces.

func Wrap

func Wrap(inner acemq.Codec, keyring *Keyring) *Codec

Wrap encrypts the output of another codec.

func (*Codec) CanDecode

func (c *Codec) CanDecode(contentType string) bool

CanDecode accepts only the encrypted content type.

func (*Codec) ContentType

func (c *Codec) ContentType() string

ContentType returns application/vnd.acemq.encrypted.

The inner codec's type is not visible on the wire, because saying "this is encrypted JSON" tells an observer more than they need. A consumer knows what to expect from its own configuration.

func (*Codec) Decode

func (c *Codec) Decode(body []byte, dst any) error

Decode decrypts and then decodes.

A body begins 0xAE, the family framing this library and Java, .NET, Python and Ruby all write. Anything else is refused, and refused as what it is rather than reported as a decryption failure.

A body that will not decrypt is fatal: the same bytes fail the same way every time, whether they were tampered with, encrypted with a key this process does not have, or were never encrypted at all.

func (*Codec) Encode

func (c *Codec) Encode(payload any) ([]byte, error)

Encode encodes and then encrypts.

The wire format is:

[0xAE][1 byte version][1 byte key id length][key id][12 byte nonce][ciphertext+tag]

The key id travels in the clear, which is the point: a consumer has to know which key to try before it can decrypt anything. It names a key rather than revealing one.

This is the only framing this library writes and the only one it reads — see Codec.Decode. Java, .NET, Python and Ruby write these same bytes.

func (*Codec) Inner

func (c *Codec) Inner() acemq.Codec

Inner is the codec whose output is encrypted.

type Key

type Key struct {
	// ID names the key on the wire, so a message says which key opens it
	// without saying anything about the key itself. Something datelike —
	// "2026-01" — reads well when somebody is working out what to rotate. At
	// most MaxKeyIDBytes bytes, because it travels in front of every message.
	ID string

	// Secret is 16, 24 or 32 bytes — the three lengths AES takes, and the same
	// three the other libraries accept. NewKey draws 32. Anything else is
	// refused rather than padded or hashed into shape, because both would make
	// a weak key look like a strong one.
	Secret []byte
}

Key is one encryption key.

func NewKey

func NewKey(id string) (Key, error)

NewKey draws a fresh random key.

func (Key) String

func (k Key) String() string

String never includes the secret.

type Keyring

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

Keyring holds the keys a process can use.

More than one, because rotation needs an overlap: new messages are written with the newest key while messages written with the old one are still being read. A keyring with one key cannot rotate without an outage.

func NewKeyring

func NewKeyring(keys ...Key) (*Keyring, error)

NewKeyring builds a keyring. The first key is the one used for writing.

func (*Keyring) Add

func (r *Keyring) Add(key Key) error

Add puts a key on the ring without making it the one used for writing.

The order rotation happens in: add the new key everywhere first, so every consumer can read it, and only then make it current somewhere.

func (*Keyring) Current

func (r *Keyring) Current() (Key, error)

Current is the key new messages are written with.

func (*Keyring) Get

func (r *Keyring) Get(id string) (Key, error)

Get looks a key up by id.

func (*Keyring) IDs

func (r *Keyring) IDs() []string

IDs lists the keys on the ring, for a health endpoint or a log line at start-up. Never the secrets.

func (*Keyring) Use

func (r *Keyring) Use(id string) error

Use makes a key the one new messages are written with.

Jump to

Keyboard shortcuts

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