Repository navigation
MessageInterception
You can intercept messages after serialization, but before the transport stores them. Use this to compress or encrypt messages as needed. The queue keeps track of which message interceptors a message used, and it re-creates them as best it can. For that to work:
- The queue must be able to create the interceptor class
- You must create any configuration classes and inject them into the queue container
There are two built-in message interceptors. A third one, 3DES, was removed in 0.16.0:
- Gzip (GZipMessageInterceptor)
This interceptor uses the built-in gzip compression class to compress messages after serialization and to decompress them on dequeue.
- AES-256-GCM encryption (AesMessageInterceptor): recommended
This interceptor uses AES-256-GCM authenticated encryption (confidentiality and tamper detection). It encrypts the message after serialization and decrypts it on dequeue. It needs a 32-byte key, which you inject through AesMessageInterceptorConfiguration. The interceptor generates a fresh random nonce for each message and stores it with the ciphertext, so there is no IV to manage. Consumers must use the same key. The queue never saves your key. You must provide it on both the producer and the consumer.
- 3DES encryption (
TripleDesMessageInterceptor): removed in 0.16.0
Removed in 0.16.0:
TripleDesMessageInterceptorno longer exists. NIST SP 800-131A retired Triple-DES, and Triple-DES is vulnerable to Sweet32.AesMessageInterceptorreplaced it in 0.9.43, and 0.16.0 removed the deprecated type. 0.15.0 is the last release that can decrypt a 3DES message. See Migrating from 3DES to AES. You must complete that migration before you upgrade past 0.15.0.
On the releases that had it, this interceptor encrypted the message after serialization and decrypted it on dequeue. It required a key and an initialization vector through TripleDesMessageInterceptorConfiguration. Consumers had to use the same key and vector.
The queue runs message interceptors in the order you provide. If you use both compression and encryption, make sure that you specify the compression module first.
var queueContainer = new QueueContainer<TTransportInit>(serviceRegister => serviceRegister.RegisterCollection<IMessageInterceptor>(new[]
{
typeof (GZipMessageInterceptor), //gzip compression
typeof (AesMessageInterceptor) //encryption (AES-256-GCM)
}).Register(() => new AesMessageInterceptorConfiguration(
RandomNumberGenerator.GetBytes(32) /* load your stable 32-byte key from a secret store */),
LifeStyles.Singleton));Security warning:
RandomNumberGenerator.GetBytes(32)above is a placeholder. It generates a new key every run. Real deployments must load a stable 32-byte key from a secrets manager or an environment variable (the producer and every consumer need the same key). Never hardcode the key in source. AES-256-GCM needs no IV. The interceptor generates a fresh nonce for each message automatically.
In the example above, we:
- register the gzip compression first
- register the encryption second
- register the encryption configuration so that we have a key to use (AES-256-GCM needs only a key, but the older 3DES interceptor also needs an initialization vector)
var queueContainer = new QueueContainer<TTransportInit>(
serviceRegister => serviceRegister.Register(() => new AesMessageInterceptorConfiguration(
yourStable32ByteKey), LifeStyles.Singleton));The consumers only need the configuration. They re-create the interceptors that the producers used, as needed. You may register the interceptors the same way as the producer, but it is not required.
Note: The same key management rules apply on the consumer side. See the security warning in the producer section above.
⚠️ Do this on 0.15.0 or earlier. Both interceptors ship there, and neither this page nor any setting brings 3DES back afterwards. Nothing in 0.16.0 can read a 3DES message.
On 0.15.0, AesMessageInterceptor and TripleDesMessageInterceptor coexist. The per-message interceptor graph records which one encrypted each message, so a consumer can decrypt both during the migration.
-
If you can drain the queue: stop producing, let consumers finish the existing 3DES messages, then replace
TripleDesMessageInterceptorwithAesMessageInterceptoreverywhere. -
If you cannot drain (long-lived messages):
-
Producers: register only
AesMessageInterceptor, so new messages are encrypted with AES (no double-encryption). -
Consumers: register both configurations:
AesMessageInterceptorConfigurationandTripleDesMessageInterceptorConfiguration. For built-in interceptors, consumers only need the configuration. The queue re-creates the interceptors as needed, and the graph selects AES for new messages and 3DES for old ones (a consumer never re-encrypts, so 3DES only ever decrypts). - Once the 3DES backlog is gone, drop
TripleDesMessageInterceptorConfigurationfrom the consumers. Only then upgrade to 0.16.0.
-
Producers: register only
Do not forget error queues and scheduled jobs that have not fired. A 3DES message sitting in either is still a 3DES message, and upgrading strands it.
To create your own message interceptor, implement IMessageInterceptor. You use it the same way as the built-in interceptors. But on the consumer, you may need to register your custom interceptor explicitly so the queue can resolve it.
public interface IMessageInterceptor
{
// Transforms bytes before storage (serialization path).
// Return MessageInterceptorResult(output, addToGraph, interceptorType).
// Set addToGraph = false to opt out (e.g., when compression is not beneficial).
MessageInterceptorResult MessageToBytes(byte[] input, IReadOnlyDictionary<string, object> headers);
// Transforms bytes on retrieval (deserialization path).
byte[] BytesToMessage(byte[] input, IReadOnlyDictionary<string, object> headers);
// Human-readable name used in logging and diagnostics.
string DisplayName { get; }
// Base type used by the queue to re-create the interceptor on the consumer side.
Type BaseType { get; }
}MessageInterceptorResult carries three values:
| Parameter | Type | Purpose |
|---|---|---|
output |
byte[] |
The transformed message bytes |
addToGraph |
bool |
When true, the interceptor records itself so consumers can reverse the transformation. Set false to skip (e.g., the GZip interceptor opts out when the compressed result would be larger than the input) |
interceptorType |
Type |
The interceptor type used for re-creation on the consumer side |
For any issues please use the GitHub issues