Creating an encrypted string type

In this age of identity theft, data security is more important than ever. Sensitive data such as credit card numbers should always be encrypted. In this recipe, we will show you how to use NHibernate to encrypt a single property.

How to do it…

  1. Create a new class library project named EncryptedStringExample.
  2. Install the NHibernate and log4net packages using the NuGet Package Manager Console by executing the following command:
    Install-Package NHibernate
    Install-Package log4net
    
  3. Add a new public interface named IEncryptor with the following three method definitions:
    public interface IEncryptor
    {
      string Encrypt(string plainText);
      string Decrypt(string encryptedText);
      string EncryptionKey { get; set; }
    }
  4. Create an implementation of IEncryptor named SymmetricEncryptorBase using the following code:
    public abstract class SymmetricEncryptorBase : IEncryptor
    {
    
      private readonly SymmetricAlgorithm _cryptoProvider;
      private byte[] _myBytes;
    
      protected SymmetricEncryptorBase(
        SymmetricAlgorithm cryptoProvider)
      {
        _cryptoProvider = cryptoProvider;
      }
    
      public string EncryptionKey { get; set; }
    
      public string Encrypt(string plainText)
      {
        var bytes = GetEncryptionKeyBytes();
        using (var memoryStream = new MemoryStream())
        {
          var encryptor = _cryptoProvider
            .CreateEncryptor(bytes, bytes);
    
          using (var cryptoStream = new CryptoStream(
            memoryStream, encryptor, CryptoStreamMode.Write))
          {
            using (var writer = new StreamWriter(cryptoStream))
            {
              writer.Write(plainText);
              writer.Flush();
              cryptoStream.FlushFinalBlock();
              return Convert.ToBase64String(
                memoryStream.GetBuffer(),
                0,
                (int) memoryStream.Length);
            }
          }
        }
      }
    
      private byte[] GetEncryptionKeyBytes()
      {
        if (_myBytes == null)
          _myBytes = Encoding.ASCII.GetBytes(EncryptionKey);
    
        return _myBytes;
      }
    
      public string Decrypt(string encryptedText)
      {
        var bytes = GetEncryptionKeyBytes();
        using (var memoryStream = new MemoryStream(
          Convert.FromBase64String(encryptedText)))
        {
          var decryptor = _cryptoProvider
            .CreateDecryptor(bytes, bytes);
          using (var cryptoStream = new CryptoStream(
            memoryStream, decryptor, CryptoStreamMode.Read))
          {
            using (var reader = new StreamReader(cryptoStream))
            {
              return reader.ReadToEnd();
            }
          }
        }
      }
    
    }
  5. Create a concrete implementation named DESEncryptor with the following code:
    public class DESEncryptor : SymmetricEncryptorBase 
    {
    
      public DESEncryptor()
        : base(new DESCryptoServiceProvider())
      { }
    
    }
  6. Add an implementation of IUserType named EncryptedString using the following code:
    public class EncryptedString : 
      IUserType, 
      IParameterizedType 
    {
    
      private IEncryptor _encryptor;
    
      public object NullSafeGet(
        IDataReader rs,
        string[] names,
        object owner)
      {
        //treat for the posibility of null values
        object passwordString =
          NHibernateUtil.String.NullSafeGet(rs, names[0]);
        if (passwordString != null)
        {
          return _encryptor.Decrypt((string)passwordString);
        }
        return null;
      }
    
      public void NullSafeSet(
        IDbCommand cmd,
        object value,
        int index)
      {
        if (value == null)
        {
          NHibernateUtil.String.NullSafeSet(cmd, null, index);
          return;
        }
    
        string encryptedValue =
          _encryptor.Encrypt((string)value);
        NHibernateUtil.String.NullSafeSet(
          cmd, 
          encryptedValue, 
          index);
      }
    
      public object DeepCopy(object value)
      {
        return value == null 
          ? null 
        : string.Copy((string)value);
      }
    
      public object Replace(object original,
        object target, object owner)
      {
        return original;
      }
    
      public object Assemble(object cached, object owner)
      {
        return DeepCopy(cached);
      }
    
      public object Disassemble(object value)
      {
        return DeepCopy(value);
      }
    
      public SqlType[] SqlTypes
      {
        get { return new[] { new SqlType(DbType.String) }; }
      }
    
      public Type ReturnedType
      {
        get { return typeof(string); }
      }
    
      public bool IsMutable
      {
        get { return false; }
      }
    
      public new bool Equals(object x, object y)
      {
        if (ReferenceEquals(x, y))
        {
          return true;
        }
        if (x == null || y == null)
        {
          return false;
        }
        return x.Equals(y);
      }
    
      public int GetHashCode(object x)
      {
        if (x == null)
        {
          throw new ArgumentNullException("x");
        }
        return x.GetHashCode();
      }
    
    
      public void SetParameterValues(
        IDictionary<string, string> parameters)
      {
        if (parameters != null)
        {
          var encryptorTypeName = parameters["encryptor"];
          _encryptor = !string.IsNullOrEmpty(encryptorTypeName)
                    ? (IEncryptor) Instantiate(encryptorTypeName)
                    : new DESEncryptor();
          var encryptionKey = parameters["encryptionKey"];
          if (!string.IsNullOrEmpty(encryptionKey))
            _encryptor.EncryptionKey = encryptionKey;
        }
        else
        {
          _encryptor = new DESEncryptor();
        }
      }
    
      private static object Instantiate(string typeName)
      {
        var type = Type.GetType(typeName);
        return Activator.CreateInstance(type);
      }
    
    }
  7. Add an entity class named Account with the following properties:
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual Money Balance { get; set; }
  8. Add a mapping document with the following XML. Don't forget to set Build Action to Embedded Resource:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="EncryptedStringExample"
        namespace="EncryptedStringExample">
      <typedef 
        name="encrypted"
        class="EncryptedStringExample.EncryptedString, EncryptedStringExample">
        <param name="encryptor">
          EncryptedStringExample.DESEncryptor, 
          EncryptedStringExample
        </param>
        <param name="encryptionKey">12345678</param>
      </typedef>
      <class name="Account">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="Name" not-null="true" />
        <property name="EMail" not-null="true" />
        <property name="CardNumber" not-null="true" type="encrypted" />
        <property name="ExpirationMonth" not-null="true" />
        <property name="ExpirationYear" not-null="true" />
        <property name="ZipCode" not-null="true" />
      </class>
    </hibernate-mapping>

How it works…

As we saw in the Mapping enumerations recipe in Chapter 2, Models and Mappings, we can set the type attribute on a property to specify a class used for converting data between our application and the database. We will use this to encrypt and decrypt our credit card number.

Our Account class mapping defined a type using the <typedef> element. The name attribute defines a nickname for our encryption type. This nickname matches the type attribute on our CardNumber property's mapping. The class attribute specifies the .NET class that will be used to convert our data in the standard namespace.typeName, assemblyName format.

Our EncryptedString type happens to use two parameters: encryptor and encryptionKey. These are set in the mapping as well.

DESEncryptor, our implementation of IEncryptor, uses DESCryptoServiceProvider to encrypt and decrypt our data. This is one of the symmetric encryption algorithms available in the .NET framework.

Our EncryptedString type implements IUserType, NHibernate's interface for defining custom types. When implementing IUserType, NullSafeGet is responsible for reading data from the ADO.NET data reader and returning an appropriate object, in this case, a string. In our EncryptedString, we read the encrypted data, use IEncryptor to decrypt it, and return the unencrypted string, which is used to set the CardNumber property. NullSafeSet takes some value, in this case, our unencrypted CardNumber, and sets a parameter on the ADO.NET command. In EncryptedString, we encrypt the card number before setting it on the command. The SqlTypes property returns an array representing the types of each database field used to store this user type. In our case, we have a single string field. The ReturnedType property returns the .NET type. Since our CardNumber is a string, we return the string type.

EncryptedString also implements IParameterizedType. The SetParameterValues method provides a dictionary of parameters from the mapping document. From that dictionary, we get the IEncryptor implementation to use, as well as the encryption key.

Note

The class mapping is not the best place to store encryption keys. This recipe can easily be adapted to read the encryption keys from a properly secured location.

There's more…

There are three categories of encryption algorithms. A symmetric algorithm uses the same key to encrypt and decrypt the data. Because our application is responsible for encryption and decryption, this type of algorithm makes the most sense.

An asymmetric algorithm encrypts data using a pair of keys: one public and one private. The key pairs are generated in such a way that the public key used to encrypt the data gives no hint as to the private key required to decrypt that data. It is not necessary to keep the public key a secret. This type of algorithm is typically used when the data is encrypted in one location and decrypted in another. System A generates the keys, holds onto the private key, and shares the public key with System B. System B encrypts data with the public key. Only System A has the private key necessary to decrypt the data.

Finally, a hash algorithm is used when the data does not need to be decrypted. With a hash algorithm, as the original data cannot be calculated from the hash value, the chance of finding different data with the same hash value is extremely small, and even a slight change in the data produces a wildly different hash value. This type of algorithm is typically used for passwords. We store the hash of the real password. When a user logs in, we do not need to know what the real password is, only that the attempted password matches the real password. We hash the attempted password. If the hash of the attempted password matches the previously stored hash of the real password, we know that the passwords match.

See also

  • Using well-known instance types
  • Using dependency injection with entities
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.255