Property encryption operates on specific properties of a message. The data in the property is encrypted, but the rest of the message is clear text. This keeps the performance impact of encryption as low as possible.
The encryption algorithm used is AES. This algorithm is based on a key shared between the sender and receiver which is known as symmetric encryption.
NServiceBus.Encryption.MessageProperty
version 3.1, the Rijndael algorithm was used to encrypt data. Microsoft has made the Rijndael
class obsolete in favor of AES and NServiceBus.Encryption.MessageProperty
version 3.1 contains both RijndaelEncryptionService
and AesEncryptionService
classes to facilitate migrating from the former to the latter. For new development, use AesEncryptionService
as the Rijndael service has been removed in later versions of the package.Keep in mind that the security is only as strong as the keys; if the key is exposed, then an attacker can decrypt the information. Encryption keys should not be stored on the client (if deployed remotely) or even on a web server in the DMZ.
endpointConfiguration.EnableMessagePropertyEncryption
.Defining encrypted properties
There are two ways of telling NServiceBus what properties to encrypt.
Convention
Given a message of this convention
public class MyMessage :
IMessage
{
public string MyEncryptedProperty { get; set; }
}
Encrypt MyEncryptedProperty
.
var encryptionService = new AesEncryptionService(
encryptionKeyIdentifier: "2015-10",
key: Convert.FromBase64String("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"));
endpointConfiguration.EnableMessagePropertyEncryption(
encryptionService: encryptionService,
encryptedPropertyConvention: propertyInfo =>
{
return propertyInfo.Name.EndsWith("EncryptedProperty");
}
);
Property type
Use the EncryptedString
type to flag that a property should be encrypted.
using NServiceBus;
using NServiceBus.Encryption.MessageProperty;
public class MyMessage :
IMessage
{
public EncryptedString MyEncryptedProperty { get; set; }
}
Enabling property encryption
Property encryption is enabled via the configuration API.
var defaultKey = "2015-10";
var encryptionService = new AesEncryptionService(
encryptionKeyIdentifier: defaultKey,
key: Convert.FromBase64String("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"));
endpointConfiguration.EnableMessagePropertyEncryption(encryptionService);
Key ID
Each key needs an unique key ID (KeyIdentifier
). The key ID is communicated in the message header and allows the receiving endpoint to use the correct decryption key.
Troubleshooting
Error: Encrypted message has no 'NServiceBus.RijndaelKeyIdentifier' header. Possibility of data corruption. Upgrade endpoints that send messages with encrypted properties.
Key IDs are only supported in the following versions
- NServiceBus 3.3.16+
- NServiceBus 4.7.8+
- NServiceBus 5.0.7+
- NServiceBus 5.1.5+
- NServiceBus 5.2.9 and newer
- NServiceBus.Encryption.MessageProperty all versions
All previous versions support decrypting messages that have encrypted fragments but no key ID header.
Key ID naming strategy
A key ID identifies which key is used and it must not expose anything about the key itself.
Good strategies
- Incremental (1, 2, 3, 4, etc.)
- Time-based (
2015-w01
,2015-m08
,2015-q03
) - Random (
ab4b7a6e71833798
),
Bad strategies
- Full hash of key (MD5, SHA-1, etc.)
- Key fragment
Using the same key with and without a key ID
If the KeyIdentifier
attribute is set for a key then it will be used to decrypt message properties with a matching key ID but it will also be used to attempt decryption for messages without a key ID.
Key format
The key format can be specified in either Base64 or ASCII format.
With ASCII its not possible to use the full 8 bit range of a byte as it is a 7 bit encoding and even then some characters need to be escaped which is not done resulting in even less characters. Only about 100 values per byte are used. For 16 byte (128 bit) keys, this means that only about 10016 out of all available 25616 combinations are used.
Defining the encryption key
When encryption is enabled, the encryption and decryption keys must be configured.
Key strength
Description | Calculation | Combinations |
---|---|---|
ASCII 16 characters | 125^16 | 3.55e+33 (7 bits minus control characters) |
ASCII 24 characters | 125^24 | 2.11e+50 (7 bits minus control characters) |
ASCII 32 characters | 125^32 | 1.26e+67 (7 bits minus control characters) |
Base64 16 bytes: | 256^16 | 3.40e+38 |
Base64 24 bytes: | 256^24 | 6.27e+57 |
Base64 32 bytes: | 256^32 | 1.16e+77 |
This means that a 16 character ASCII key is almost 100.000 times weaker then a 16 byte Base64 key.
Configuration
Via Code
var defaultKey = "2015-10";
var keys = new Dictionary<string, byte[]>
{
{"2015-10", Convert.FromBase64String("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")},
{"2015-09", Convert.FromBase64String("abDbqRpQdRbTs3mhdZh9qCaDaxJXl+e6")},
{"2015-08", Convert.FromBase64String("cdDbqRpQdRbTs3mhdZh9qCaDaxJXl+e6")},
};
var encryptionService = new AesEncryptionService(defaultKey, keys);
endpointConfiguration.EnableMessagePropertyEncryption(encryptionService);
Via App.config or IProvideConfiguration
Configuring via an app.
file or with IProvideConfiguration
is not available. Move to code-based configuration.
Multi-Key decryption
Several of the examples above used both an encryption key and multiple decryption keys.
This feature allows a phased approach to key rotation. Endpoints can update to a new encryption key and maintain ability to decrypt information sent by endpoints using old keys.
When the original encryption key is replaced by a new encryption key, in-flight messages that were encrypted with the original key will not be decrypted unless the original encryption key is added to a list of decryption keys.
Custom handling of property encryption
To take full control over how properties are encrypted replace the IEncryptionService
instance.
This allows explicit handling of the encryption and decryption of each value. So for example to use an algorithm other than AES.
endpointConfiguration.EnableMessagePropertyEncryption(new EncryptionService());
using NServiceBus.Pipeline;
using EncryptedValue = NServiceBus.Encryption.MessageProperty.EncryptedValue;
using IEncryptionService = NServiceBus.Encryption.MessageProperty.IEncryptionService;
public class EncryptionService :
IEncryptionService
{
public EncryptedValue Encrypt(string value, IOutgoingLogicalMessageContext context)
{
throw new NotImplementedException();
}
public string Decrypt(EncryptedValue encryptedValue, IIncomingLogicalMessageContext context)
{
throw new NotImplementedException();
}
}