Skip to content
Perspectivas e Iteraciones Entendiendo la IA: técnico, cotidiano y reflexiones.
← atrás

Implementando Cifrado Transparente con Listeners (Interceptores) de NHibernate

3 de noviembre de 2014 • 5 min de lectura

Implementando Cifrado Transparente con Listeners (Interceptores) de NHibernate

¿Alguna vez has tenido que cifrar datos en la base de datos? En este post, exploraré cómo usar nHibernate Listeners para cifrar y descifrar datos que provienen y van hacia tu base de datos. La criptografía será transparente para tu aplicación.

¿Por qué querrías hacer esto? SQL Server tiene cifrado integrado en el producto. Eso es cierto, pero si te estás moviendo a la nube y quieres usar SQL Azure necesitarás algún tipo de estrategia de criptografía. SQL Azure no soporta cifrado de base de datos.

¿Qué es un Listener de nHibernate? Pienso en un Listener como un pedazo de código que puedo inyectar en puntos específicos de extensibilidad en el ciclo de vida de persistencia e hidratación de datos de nHibernate.

Al momento de escribir esto, los siguientes puntos de extensibilidad están disponibles en nHibernate.

  • IAutoFlushEventListener
  • IDeleteEventListener
  • IDirtyCheckEventListener
  • IEvictEventListener
  • IFlushEntityEventListener
  • IFlushEventListener
  • IInitializeCollectionEventListener
  • ILoadEventListener
  • ILockEventListener
  • IMergeEventListener
  • IPersistEventListener
  • IPostCollectionRecreateEventListener
  • IPostCollectionRemoveEventListener
  • IPostCollectionUpdateEventListener
  • IPostDeleteEventListener
  • IPostInsertEventListener
  • IPostLoadEventListener
  • IPostUpdateEventListener
  • IPreCollectionRecreateEventListener
  • IPreCollectionRemoveEventListener
  • IPreCollectionUpdateEventListener
  • IPreDeleteEventListener
  • IPreInsertEventListener
  • IPreLoadEventListener
  • IPreUpdateEventListener
  • IRefreshEventListener
  • IReplicateEventListener
  • ISaveOrUpdateEventListener

La lista es extensa.

Para implementar criptografía transparente, necesitamos encontrar el lugar correcto para cifrar y descifrar los datos. Para cifrar los datos usaremos IPostInsertEventListener e IPostUpdateEventListener. Con estos eventos capturaremos los datos nuevos y los datos actualizados que van hacia la base de datos. Para descifrar, usaremos el IPreLoadEventListener.

Para esta demostración usaremos la clase DatabaseCryptography para cifrar y descifrar. La implementación de criptografía no es importante para este artículo.

IPreLoadEventListener

public class PreLoadEventListener : IPreLoadEventListener
{
readonly DatabaseCryptography _crypto = new DatabaseCryptography();

///
/// Called when [pre load].
///

///The event. public void OnPreLoad(PreLoadEvent @event)
{
_crypto.DecryptProperty(@event.Entity, @event.Persister.PropertyNames, @event.State);
}
}

IPreInsertEventListener

public class PreInsertEventListener : IPreInsertEventListener
{
readonly DatabaseCryptography _crypto = new DatabaseCryptography();

///
/// Return true if the operation should be vetoed
///

///The event. /// true if XXXX, false otherwise.
public bool OnPreInsert(PreInsertEvent @event)
{
_crypto.EncryptProperties(@event.Entity, @event.State, @event.Persister.PropertyNames);

return false;
}
}

IPreUpdateEventListener

public class PreUpdateEventListener : IPreUpdateEventListener
{
readonly DatabaseCryptography _crypto = new DatabaseCryptography();

///
/// Return true if the operation should be vetoed
///

///The event. /// true if XXXX, false otherwise.
public bool OnPreUpdate(PreUpdateEvent @event)
{
_crypto.EncryptProperties(@event.Entity, @event.State, @event.Persister.PropertyNames);

return false;
}
}

Es importante notar que tanto IPreUpdateEventListener como IPreInsertEventListener deben retornar false, de lo contrario el evento de inserción/actualización será abortado.

Ahora que tenemos los Listeners implementados necesitamos registrarlos con nHibernate. Estoy usando FluentNHibernate así que esto será diferente si estás usando nHibernate puro.

SessionFactory

public class SessionFactory
{
///
/// Creates the session factory.
///

/// ISessionFactory.
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()

.Database(MsSqlConfiguration.MsSql2012
.ConnectionString(c => c
.FromConnectionStringWithKey("DefaultConnection")))

.Mappings(m => m.FluentMappings.AddFromAssemblyOf())
.ExposeConfiguration(s =>
{
s.SetListener(ListenerType.PreUpdate, new PreUpdateEventListener());
s.SetListener(ListenerType.PreInsert, new PreInsertEventListener());
s.SetListener(ListenerType.PreLoad, new PreLoadEventListener());
})
.BuildConfiguration()
.BuildSessionFactory();
}

Cuando desciframos y ciframos datos a nivel de aplicación hace que los datos sean inútiles en la base de datos. Necesitarás traer los datos de vuelta a la aplicación para leer los valores de los campos cifrados. Queremos limitar los campos que son cifrados y solo queremos cifrar valores de cadena. Cifrar cualquier cosa que no sean valores de cadena complica las cosas. No hay nada que diga que no podemos cifrar fechas, pero hacerlo requerirá que el campo de fecha en la base de datos se convierta en un campo de cadena (nvarchar o varchar), para contener los datos cifrados, una vez que hagamos esto perdemos la capacidad de operar en el campo de fecha desde la base de datos.

Para identificar qué campos queremos cifrados y descifrados usaré atributos marcadores.

Atributo Encrypt

public class EncryptAttribute : Attribute
{
}

Atributo Decrypted

public class DecryptAttribute : Attribute
{
}

Para ver el EncryptAttribute y el DecryptedAttribute en acción echaremos un vistazo a la clase DatabaseCryptography.

DatabaseCryptography

public class DatabaseCryptography
{
    private readonly Crypto _crypto = ObjectFactory.GetInstance();

    ///
    /// Encrypts the properties.
    ///
    ///The entity. ///The state. ///The property names. 
    public void EncryptProperties(object entity, object[] state, string[] propertyNames)
    {
        Crypt(entity, propertyNames, s = >
        _crypto.Encrypt(s),
        state)
        ;
    }

    ///
    /// Crypts the specified entity.
    ///

    ///
    ///The entity. ///The state. ///The property names. ///The crypt.
    private void Crypt(object entity, string[] propertyNames, Func<string, string> crypt, object[] state) where T : Attribute
    {
        if (entity != null)
        {
            var properties = entity.GetType().GetProperties();

            foreach (var info in properties)
            {
                var attributes = info.GetCustomAttributes(typeof (T), true);

                if (attributes.Any())
                {
                    var name = info.Name;
                    var count = 0;

                    foreach (var s in propertyNames)
                    {
                        if (string.Equals(s, name, StringComparison.InvariantCultureIgnoreCase))
                        {
                            var val = Convert.ToString(state[count]);
                            if (!string.IsNullOrEmpty(val))
                            {

                                val = crypt(val);
                                state[count] = val;
                            }

                            break;
                        }

                        count++;
                    }
                }
            }
        }
    }

    ///
    /// Decrypts the property.
    ///
    ///The entity. ///The state. ///The property names. 
    public void DecryptProperies(object entity, string[] propertyNames, object[] state)
    {
        Crypt(entity, propertyNames, s = >
        _crypto.Decrypt(s),
        state)
        ;
    }

}

Eso es todo. Ahora el cifrado y descifrado de datos será transparente para la aplicación y puedes continuar alegremente construyendo el próximo Facebook.

↑ Volver arriba

Autor: Chuck Conway se especializa en ingeniería de software e IA Generativa. Conéctate con él en redes sociales: X (@chuckconway) o visítalo en YouTube.