Posts
Implementando Criptografia Transparente com Ouvintes do NHibernate (Interceptadores)
3 de novembro de 2014 • 5 min de leitura
Você já precisou criptografar dados no banco de dados? Neste artigo, vou explorar como usar nHibernate Ouvintes para criptografar e descriptografar dados que vêm e vão para seu banco de dados. A criptografia será transparente para sua aplicação.
Por que você gostaria de fazer isso? SQL Server tem criptografia integrada no produto. Isso é verdade, mas se você está migrando para a nuvem e quer usar SQL Azure, você precisará de alguma estratégia de criptografia. SQL Azure não suporta criptografia de banco de dados.
O que é um Ouvinte do nHibernate? Penso em um Ouvinte como um pedaço de código que posso injetar em pontos de extensibilidade específicos no ciclo de vida de persistência e hidratação de dados do nHibernate.
No momento desta escrita, os seguintes pontos de extensibilidade estão disponíveis no nHibernate.
IAutoFlushEventListenerIDeleteEventListenerIDirtyCheckEventListenerIEvictEventListenerIFlushEntityEventListenerIFlushEventListenerIInitializeCollectionEventListenerILoadEventListenerILockEventListenerIMergeEventListenerIPersistEventListenerIPostCollectionRecreateEventListenerIPostCollectionRemoveEventListenerIPostCollectionUpdateEventListenerIPostDeleteEventListenerIPostInsertEventListenerIPostLoadEventListenerIPostUpdateEventListenerIPreCollectionRecreateEventListenerIPreCollectionRemoveEventListenerIPreCollectionUpdateEventListenerIPreDeleteEventListenerIPreInsertEventListenerIPreLoadEventListenerIPreUpdateEventListenerIRefreshEventListenerIReplicateEventListenerISaveOrUpdateEventListener
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 dados novos e atualizados indo para o banco de dados. Para descriptografar, usaremos o IPreLoadEventListener.
Para esta demonstração, estaremos usando a classe DatabaseCryptography para criptografar e descriptografar. A implementação de 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 Ouvintes 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 de string. Criptografar qualquer coisa que não seja valores de 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 manter os dados criptografados, uma vez que fazemos isso, perdemos a capacidade de operar no campo de data do banco de dados.
Para identificar quais campos queremos criptografados e descriptografados, usarei atributos marcadores.
Encrypt Attribute
public class EncryptAttribute : Attribute
{
}
Decrypted Attribute
public class DecryptAttribute : Attribute
{
}
Para ver o EncryptAttribute e o DecryptedAttribute em ação, vamos dar 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)
;
}
}
Pronto. Agora a criptografia e descriptografia de dados será transparente para a aplicação e você pode continuar seu caminho construindo o próximo Facebook.
Autor: Chuck Conway é um Engenheiro de IA com quase 30 anos de experiência em engenharia de software. Ele constrói sistemas de IA práticos—pipelines de conteúdo, agentes de infraestrutura e ferramentas que resolvem problemas reais—e compartilha o que está aprendendo ao longo do caminho. Conecte-se com ele nas redes sociais: X (@chuckconway) ou visite-o no YouTube e no SubStack.