Class EncryptedCodec
- All Implemented Interfaces:
Codec
Codec encrypted = EncryptedCodec.wrapping(new JsonCodec(), keyring);
mq.publisher("payments", "card.stored", Card.class).as(encrypted);
mq.consume("payments.stored", Card.class, encrypted, m -> store(m.payload()));
It wraps a delegate rather than serialising anything itself, so choosing a format and choosing to encrypt stay independent: JSON in, AES-GCM out, and Avro just as well.
What is on the wire
0xAE 0x01 len key identifier 12-byte nonce ciphertext + 16-byte tag
The key identifier is in the message, in the clear. That is deliberate, and it is what makes rotation possible: a consumer reads which key a message needs rather than assuming the current one, so a new key can be introduced while messages written with the old one are still queued. Putting it in an AMQP header instead would have been tidier and would have lost it — headers are dropped by shovels, rewritten by federation, and absent from a message recovered out of a backup, and a ciphertext whose key nobody can name is gone.
The header is authenticated but not encrypted: GCM binds it as associated data, so an altered key identifier fails to decrypt rather than quietly decrypting as something else.
What this does not do
The broker can no longer read the message, and neither can the people who operate it.
Decide what they do instead before turning this on: a dead-letter queue full
of ciphertext is a queue nobody can triage, and the answer is usually a small internal tool
holding the keyring rather than the management UI. See
keyIdOf(byte[]), which tells an operator which key a message needs without holding
any of them.
Encryption is not authorisation. Every service holding the keyring can read every message encrypted with those keys; the granularity is the key, so separate audiences mean separate keys.
Nor does it hide the routing. Exchange, routing key, headers and message size stay in the clear, and for many systems the routing key is the sensitive part.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringDeliberately not...+json, whatever the plaintext underneath is. -
Method Summary
Modifier and TypeMethodDescriptionboolean<T> Tdelegate()byte[]static @Nullable StringkeyIdOf(byte[] body) Reads which key a message needs, without needing the key.toString()static EncryptedCodec
-
Field Details
-
CONTENT_TYPE
Deliberately not...+json, whatever the plaintext underneath is.A
+jsonsuffix is a promise that the bytes on the wire are JSON, and every JSON-aware consumer reads it that way. These bytes are ciphertext. Naming them+jsonmakes the JSON codec volunteer to decode them, which is how a message ends up failing in a parser rather than being refused by a codec that knows it cannot help.- See Also:
-
-
Method Details
-
wrapping
- Parameters:
delegate- the codec that turns objects into bytes; those bytes are what gets encryptedkeyring- the keys to write with and read with- Returns:
- a codec that encrypts the delegate's output
-
contentType
- Specified by:
contentTypein interfaceCodec- Returns:
- the content type this codec produces, for example
application/json
-
encode
-
decode
-
canDecode
-
keyIdOf
Reads which key a message needs, without needing the key.For the operator looking at a dead-letter queue they can no longer read. The identifier is in the clear in front of the ciphertext, so this answers "which key does this need?" from the bytes alone — which is usually the question, because a queue full of undecryptable messages is normally a key that was retired too early rather than anything wrong with the messages.
- Parameters:
body- a message body- Returns:
- the key identifier, or
nullif this was not written by this codec
-
delegate
- Returns:
- the wrapped codec, whose output is what gets encrypted
-
toString
-