Skip to content

MessageInterception

Brian Lehnen edited this page Jul 7, 2026 · 7 revisions

Message Interception

Messages can be intercepted after serialization, but before the transport stores them. This can be used to compress or encrypt messages as needed. The queue will keep track of which message interceptors a message used, and re-create them as best it can. In order for that to work

  • The queue must be able to create the interceptor class
  • Any configuration classes must created and injected into the queue container

There are three built in message interceptors

  • Gzip (GZipMessageInterceptor)

This interceptor will use the build in gzip compression class to compress messages after serialization and decompress on dequeue.

  • AES-256-GCM encryption (AesMessageInterceptor) — recommended

This interceptor encrypts the message after serialization and decrypts on dequeue using AES-256-GCM authenticated encryption (confidentiality and tamper detection). It requires a 32-byte key, injected via AesMessageInterceptorConfiguration. A fresh random nonce is generated per message and stored with the ciphertext — 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 consumer.

  • 3DES encryption (TripleDesMessageInterceptor) — deprecated

Deprecated: TripleDesMessageInterceptor is obsolete (Triple-DES is retired by NIST SP 800-131A and is vulnerable to Sweet32) and will be removed in a future major version. Use AesMessageInterceptor for new work. 3DES is retained only so existing 3DES-encrypted messages remain decryptable — see Migrating from 3DES to AES.

This interceptor encrypts the message after serialization and decrypts on dequeue. It requires a key and initialization vector via TripleDesMessageInterceptorConfiguration; consumers must use the same key and vector.

Message interceptors are ran in the order you provide. So, if you are using both compression and encryption, make sure you specify the compression module first.

Registering the interceptors - Producer
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 environment variable (the producer and every consumer need the same key), never hardcoded in source. AES-256-GCM needs no IV — a fresh per-message nonce is generated automatically.

In the above example, we are

  • registering the gzip compression first
  • registering the encryption second
  • registering the encryption configuration so that we have a key to use (AES-256-GCM needs only a key; the older 3DES interceptor also needs an initialization vector)
Registering the interceptors - Consumer
var queueContainer = new QueueContainer<TTransportInit>(
serviceRegister => serviceRegister.Register(() => new AesMessageInterceptorConfiguration(
    yourStable32ByteKey), LifeStyles.Singleton));

The consumers only need the configuration. They will re-create the interceptors used by the producers as needed. You may register them just like the producer, but it's not required.

Note: The same key management rules apply on the consumer side — see the security warning in the producer section above.

Migrating from 3DES to AES

AesMessageInterceptor and TripleDesMessageInterceptor coexist: the per-message interceptor graph records which one encrypted each message, so a consumer can decrypt both during a migration.

  • If you can drain the queue: stop producing, let consumers finish the existing 3DES messages, then replace TripleDesMessageInterceptor with AesMessageInterceptor everywhere.
  • If you cannot drain (long-lived messages):
    • Producers: register only AesMessageInterceptor — new messages are encrypted with AES (no double-encryption).
    • Consumers: register both configurationsAesMessageInterceptorConfiguration and TripleDesMessageInterceptorConfiguration. As noted above, consumers only need the configuration for built-in interceptors; 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 the TripleDesMessageInterceptorConfiguration from the consumers.
Custom interceptor

You can create your own message interceptors by implementing IMessageInterceptor. Usage is the same as the built-in interceptors; however, for 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

Note that interceptors run in the order they are registered. If both compression and encryption are used, register compression first so it runs before encryption.

Clone this wiki locally