muonlab » 2010 » April

random .NET and web development musings

The simplest way I’ve found to encrypt data with NHibernate is to use a custom UserType which encrypts/decrypts the data on read/write.

As this post on stackoverflow demonstrates, it’s rather easy to make such a UserType. However, what I didn’t want to have to do is make a different UserType for each different property type that I want to encrypt.

Enter Encrypted<T>:

public class Encrypted<T> : PrimitiveType
{
	private readonly IEncryptionService encrypter;
	private readonly IBinaryConverter converter;

	public Encrypted() : base(new BinarySqlType())
	{
		this.encrypter = ServiceLocator.Current.GetInstance<IEncryptionService>();
		this.converter = ServiceLocator.Current.GetInstance<IBinaryConverter>();
	}

	public override string Name
	{
		get { return typeof (T).Name; }
	}

	public override Type ReturnedClass
	{
		get { return typeof (T); }
	}

	public override Type PrimitiveClass
	{
		get { return typeof (T); }
	}

	public override object DefaultValue
	{
		get { return default(T); }
	}

	public override void Set(IDbCommand cmd, object value, int index)
	{
		var serialized = this.converter.Serialize(value);
		var encrypted = this.encrypter.Encrypt(serialized);

		((IDataParameter) cmd.Parameters[index]).Value = encrypted;
	}

	public override object Get(IDataReader rs, int index)
	{
		if (rs.IsDBNull(index))
			return null;

		var encrypted = rs[index] as byte[];

		var decrypted = this.encrypter.Decrypt(encrypted);
		var deserialized = this.converter.Deserialize(decrypted);

		return deserialized;
	}

	public override object Get(IDataReader rs, string name)
	{
		return this.Get(rs, rs.GetOrdinal(name));
	}

	public override object FromStringValue(string xml)
	{
		if (xml == null)
			return null;

		if (xml.Length % 2 != 0)
			throw new ArgumentException("The string is not a valid xml representation of a binary content.", "xml");

		var bytes = new byte[xml.Length / 2];
		for (var i = 0; i < bytes.Length; i++)
		{
			var hexStr = xml.Substring(i * 2, (i + 1) * 2);
			bytes[i] = (byte) (byte.MinValue + byte.Parse(hexStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture));
		}

		var decrypted = this.encrypter.Decrypt(bytes);
		var deserialized = this.converter.Deserialize(decrypted);

		return deserialized;
	}

	public override string ObjectToSQLString(object value, Dialect dialect)
	{
		var bytes = value as byte[];
		if (bytes == null)
			return "NULL";

		var builder = new StringBuilder();

		for (int i = 0; i < bytes.Length; i++)
		{
			string hexStr = (bytes[i] - byte.MinValue).ToString("x", CultureInfo.InvariantCulture);
			if (hexStr.Length == 1)
				builder.Append('0');

			builder.Append(hexStr);
		}

		return builder.ToString();
	}
}

The implementations of IBinaryConverter and IEncryptionService aren’t important for the purposes of this post, here are their signatures so you can implement then how you like:

public interface IBinaryConverter
{
	byte[] Serialize(object obj);
	object Deserialize(byte[] bytes);
}

public interface IEncryptionService
{
	byte[] Encrypt(byte[] plain);
	byte[] Decrypt(byte[] cipher);
}