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 ¶
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.
const KeySize = 32
KeySize is the length of the key NewKey draws. AES-256.
It is not the only length accepted: see Key.Secret.
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 ¶
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 (*Codec) ContentType ¶
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 ¶
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 ¶
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.
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.
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 ¶
NewKeyring builds a keyring. The first key is the one used for writing.
func (*Keyring) Add ¶
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.