NServiceBus and encrypting strings in messages, plus how to remove NServiceBus from your messages library

Summary

There are a few blog posts about using NServiceBus and encrypting strings using the WireEncryptedString type. However, you can also mark an ordinary string to be encrypted via configuration, which is more desirable as you can remove the dependancy from NServiceBus from your common Messages library.
There is a gotcha though, if you ever mix the two, then WireEncryptedString go back to being unencrypted in MSMQ. This blog post explains how to easily fix this, and also how to remove NServiceBus from your Messages library. Win Win.

Source code is avilable here. Compiling the project will automatically download packages via NuGet.

Initial project setup

Always have a separate Messages project to keep your messages in. In this example, the project is called Messages and contains the following class:
using NServiceBus;
 
namespace Messages
{
    public class MyMessage : IMessage
    {
        public WireEncryptedString Secret1 { getset; }
        public string PlainString { getset; }
    }
}

Note there it inherits from IMessage, and therefore has a dependance on NServiceBus. It also makes use of the WireEncryptedString which again has a dependance on NServiceBus.

Nothing special in the app.config other than to specify the message endpoint as SomeQueueName.
<configuration>
    <configSections>
        <section name="UnicastBusConfig" 
                 type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
    </configSections>
    
    <UnicastBusConfig>
        <MessageEndpointMappings>
            <add Messages="Messages" Endpoint="SomeQueueName" />
        </MessageEndpointMappings>
    </UnicastBusConfig>
</configuration> 

The endpoint configuration specifies that NServiceBus use Rijndael as the encryption service:
public class EndpointConfig : 
    IConfigureThisEndpoint, 
    AsA_Client, 
    IWantCustomInitialization
{
    public void Init()
    {
        Configure.With()
                 .DefaultBuilder()
                 .RijndaelEncryptionService();
    }
}

We also need to setup an encryption key. This is done by the following class:
public class ConfigOverride : 
    IProvideConfiguration<RijndaelEncryptionServiceConfig>
{
    public RijndaelEncryptionServiceConfig GetConfiguration()
    {
        return new RijndaelEncryptionServiceConfig
            {
                // This key could be fetched from a REST/WS call, 
                // Database, or a common xml/settings file.
                Key = "blah.blah.blah.blah.blah.blah..."
            };
    }
}

The final piece is to create a class that will actually send a message:
public class MessageSender : IWantToRunWhenBusStartsAndStops
{
    public IBus Bus { getset; }

    public void Start()
    {
        var msg = new MyMessage
            {
                Secret1 = "You should not be able to read this in MSMQ",
                PlainString = "This is an ordinary string"
            };
        Bus.Send(msg);
        LogManager.GetLogger("MessageSender").Info("Sent message.");
    }

    public void Stop()
    {
    }
}

The resultant message looks like this:
<MyMessage>
    <Secret1>
        <EncryptedValue>
            <EncryptedBase64Value>QcJJfYP2SlOuDYNgFTQ8t8XI5D7zlwQzQXEFk8hcYT7gKUvqxN2Jg1UT7Q1CO929</EncryptedBase64Value>
            <Base64Iv>Q9Dc+k4V4LLPUc5+lEAtOQ==</Base64Iv>
        </EncryptedValue>
    </Secret1>
    <PlainString>This is an ordinary string</PlainString>
</MyMessage>

So far so good.

Now let's mark the PlainString to be encrypted using configuration instead of WireEncryptedString.
The first thing we do is to create a custom attribute we can use to mark strings to be encrypted:
namespace Messages
{
    public class MyEncryptionAttribute : Attribute
    {
    }
}

Then mark the strings to be encrypted:
public class MyMessage : IMessage
{
    public WireEncryptedString Secret1 { getset; }
    
    [MyEncryption]
    public string PlainString { getset; }
}

Next, we need to tell NServiceBus to treat all fields that use MyEncryption attributes to be encrypted:

namespace Sender
{
    public class ConventionsConfiguration : 
        IWantToRunBeforeConfiguration
    {
        public void Init()
        {
            Configure
                .Instance
                .DefiningEncryptedPropertiesAs(type => 
                    type.GetCustomAttributes(true)
                    .Any(t => t.GetType().Name == "MyEncryptionAttribute"));
        }
    }
}

So, run it and lets see what we get:
<MyMessage>
    <Secret1>
        <Value>You should not be able to read this in MSMQ</Value>
    </Secret1>
    <PlainString>k9FJRIfCMmyOW4ev8wytLIg56wEKsSbg1stmSo8Wu44=@XEx7Gkaks5nfhrQAuS2kZw==</PlainString>
</MyMessage>

Whoops!

The PlainString is now encrypted as we want, but in doing so the Secret1 string has become plain text. The Secret1 string is still a WireEncryptedString type and yet it still insists on being plain text, just because we've used the DefiningEncryptedPropertiesAs property on NServiceBus.


Let's fix it

We do this by changing ConventionsConfiguration class to also include the WireEncryptedString type as follows:
public class ConventionsConfiguration : 
    IWantToRunBeforeConfiguration
{
    public void Init()
    {
        Configure
            .Instance
            .DefiningEncryptedPropertiesAs(type =>
                type.PropertyType.Name == "WireEncryptedString" ||
                type.GetCustomAttributes(true)
                .Any(t => t.GetType().Name == "MyEncryptionAttribute"));
    }
}

Running this, the message now appears as:
<MyMessage>
 <Secret1>
  <EncryptedValue>
   <EncryptedBase64Value>4PHC9vcbOID08b8wbCl9tHdv8VH0aW8lFRFYGyHodwFkaV+YzF07aPPeRS3PVSCY</EncryptedBase64Value>
   <Base64Iv>/oFdQX9eTv7WWPJZIRwYsw==</Base64Iv>
  </EncryptedValue>
 </Secret1>
 <PlainString>P5vhzqd3NibLovHYMk8cVI/9k60jVDPERMHRPMMuCmg=@qbwJ2hrdeXegHbkisO4J8w==</PlainString>
</MyMessage>

Voila!

Both strings are now encrypted. You can see they are slightly different in the message.
To access Secret1, you do it via the .Value() method on Secret1. To access the plain unencrypted text on PlainString, you simply access the string as normal. Both strings are encrypted and unencrypted for you by NServiceBus.

How to remove NServiceBus from our Messages library

Now we have a way to tag strings to be encrypted, we can alter our message class to be:
public class MyMessage : IMessage
{
    [MyEncryption]
    public string Secret1 { getset; }
    
    [MyEncryption]
    public string PlainString { getset; }
}

Next we need to remove the IMessage interface, and remove all dependancies from NServiceBus. In a similar fashion to marking strings with attributes, we can mark message classes with an attribute.
public class MessageAttribute : Attribute
{
}

The we tag the message as follows:
[Message]
public class MyMessage
{
    [MyEncryption]
    public string Secret1 { getset; }
    
    [MyEncryption]
    public string PlainString { getset; }
}

NServiceBus now needs to be told which messages it can use. This is done in our ConventionsConfiguration class by adding a call to .DefinishMessageAs() as follows:
public class ConventionsConfiguration : 
    IWantToRunBeforeConfiguration
{
    public void Init()
    {
        Configure
            .Instance
            .DefiningEncryptedPropertiesAs(type =>
                type.PropertyType.Name == "WireEncryptedString" ||
                type.GetCustomAttributes(true)
                    .Any(t => t.GetType().Name == "MyEncryptionAttribute"))
            .DefiningMessagesAs(type =>
                type.GetCustomAttributes(true)
                    .Any(t => t.GetType().Name == "MessageAttribute"));
    }
}

Let's run this to see what the message now looks like:
<MyMessage>
    <Secret1>Rv7L0pDwDlY9KOZQqXuoRPGnD8MxXUJ7dPCOfXnBpP337Egz59aGMY/z6yhW03op@CAeXrzjYBwXYiN2R1iGKrw==</Secret1>
    <PlainString>obwa5exdHIt7AFoQKypzSZIRQR/Q2srJgP3Valej54o=@T09tOdiPjMxFntdI1FJouw==</PlainString>
</MyMessage>

That's it

  1. I've removed all references to NServiceBus from my messages library. So no more messy versioning problems for other projects.
  2. Strings to be encrypted are tagged with an attribute, which we tell NServiceBus about.
  3. Message classes are tagged with an attribute, which we tell NServiceBus about.

Popular posts from this blog

Execution of user code in the .NET Framework is disabled

Service Broker sys.transmission_queue clean up

What do I use to write software?