Skip to content

Posts

Implementando Criptografia Transparente com Listeners (Interceptors) do NHibernate

3 de novembro de 2014 • 5 min de leitura

Implementando Criptografia Transparente com Listeners (Interceptors) do NHibernate

Você já precisou criptografar dados no banco de dados? Neste post, vou explorar como usar nHibernate Listeners para criptografar e descriptografar dados que vêm e vão para o seu banco de dados. A criptografia será transparente para sua aplicação.

Por que você gostaria de fazer isso? O SQL Server tem criptografia integrada no produto. Isso é verdade, mas se você está migrando para a nuvem e quer usar o SQL Azure, precisará de algum tipo de estratégia de criptografia. O SQL Azure não suporta criptografia de banco de dados.

O que é um Listener do nHibernate? Eu penso em um Listener como um pedaço de código que posso injetar em pontos específicos de extensibilidade no ciclo de vida de persistência e hidratação de dados do nHibernate.

No momento da escrita deste artigo, os seguintes pontos de extensibilidade estão disponíveis no 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

A lista é extensa.

Para implementar criptografia transparente, precisamos encontrar o lugar certo para criptografar e descriptografar os dados. Para criptografar os dados usaremos IPostInsertEventListener e IPostUpdateEventListener. Com esses eventos capturaremos os novos dados e os dados atualizados que vão para o banco de dados. Para descriptografar, usaremos o IPreLoadEventListener.

Para esta demonstração usaremos a classe DatabaseCryptography para criptografar e descriptografar. A implementação da criptografia não é importante para este artigo.

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;
}
}

É importante notar que tanto IPreUpdateEventListener quanto IPreInsertEventListener devem retornar false, caso contrário o evento de inserção/atualização será abortado.

Agora que temos os Listeners implementados, precisamos registrá-los com o nHibernate. Estou usando FluentNHibernate, então isso será diferente se você estiver 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();
}

Ao descriptografar e criptografar dados no nível da aplicação, isso torna os dados inúteis no banco de dados. Você precisará trazer os dados de volta para a aplicação para ler os valores dos campos criptografados. Queremos limitar os campos que são criptografados e queremos criptografar apenas valores string. Criptografar qualquer coisa além de valores string complica as coisas. Não há nada dizendo que não podemos criptografar datas, mas fazer isso exigirá que o campo de data no banco de dados se torne um campo string (nvarchar ou varchar), para armazenar os dados criptografados. Uma vez que fazemos isso, perdemos a capacidade de operar no campo de data a partir do banco de dados.

Para identificar quais campos queremos criptografados e descriptografados, usarei atributos marcadores.

Atributo Encrypt

public class EncryptAttribute : Attribute
{
}

Atributo Decrypted

public class DecryptAttribute : Attribute
{
}

Para ver o EncryptAttribute e o DecryptedAttribute em ação, daremos uma olhada na classe 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)
        ;
    }

}

É isso. Agora a criptografia e descriptografia de dados será transparente para a aplicação e você pode seguir seu caminho feliz construindo o próximo Facebook.

Autor: Chuck Conway é especialista em engenharia de software e IA Generativa. Conecte-se com ele nas redes sociais: X (@chuckconway) ou visite-o no YouTube.

↑ Voltar ao topo

Você também pode gostar