Posts
Implementando Criptografia Transparente com Listeners (Interceptors) do NHibernate
3 de novembro de 2014 • 5 min de leitura

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.