Посты
Реализация прозрачного шифрования с помощью NHibernate Listeners (Interceptors)
3 ноября 2014 г. • 4 мин чтения
Приходилось ли вам когда-нибудь шифровать данные в базе данных? В этой статье я рассмотрю, как использовать nHibernate Listeners для шифрования и расшифровки данных, поступающих из базы данных и записываемых в неё. Криптография будет прозрачна для вашего приложения.
Зачем это нужно? SQL Server имеет встроенное шифрование. Это правда, но если вы переходите в облако и хотите использовать SQL Azure, вам потребуется какая-то стратегия криптографии. SQL Azure не поддерживает шифрование базы данных.
Что такое nHibernate Listener? Я думаю о Listener как о коде, который я могу внедрить в определённые точки расширяемости в жизненном цикле сохранения и гидратации данных nHibernate.
На момент написания этой статьи в nHibernate доступны следующие точки расширяемости.
IAutoFlushEventListenerIDeleteEventListenerIDirtyCheckEventListenerIEvictEventListenerIFlushEntityEventListenerIFlushEventListenerIInitializeCollectionEventListenerILoadEventListenerILockEventListenerIMergeEventListenerIPersistEventListenerIPostCollectionRecreateEventListenerIPostCollectionRemoveEventListenerIPostCollectionUpdateEventListenerIPostDeleteEventListenerIPostInsertEventListenerIPostLoadEventListenerIPostUpdateEventListenerIPreCollectionRecreateEventListenerIPreCollectionRemoveEventListenerIPreCollectionUpdateEventListenerIPreDeleteEventListenerIPreInsertEventListenerIPreLoadEventListenerIPreUpdateEventListenerIRefreshEventListenerIReplicateEventListenerISaveOrUpdateEventListener
Список довольно обширный.
Для реализации прозрачной криптографии нам нужно найти правильное место для шифрования и расшифровки данных. Для шифрования данных мы будем использовать IPostInsertEventListener и IPostUpdateEventListener. С помощью этих событий мы перехватим новые данные и обновлённые данные, поступающие в базу данных. Для расшифровки мы будем использовать IPreLoadEventListener.
Для этой демонстрации мы будем использовать класс DatabaseCryptography для шифрования и расшифровки. Реализация криптографии не важна для этой статьи.
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;
}
}
Важно отметить, что IPreUpdateEventListener и IPreInsertEventListener должны возвращать false, иначе событие insert/update будет отменено.
Теперь, когда у нас реализованы Listeners, нам нужно зарегистрировать их в nHibernate. Я использую FluentNHibernate, поэтому это будет отличаться, если вы используете raw nHibernate.
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();
}
При расшифровке и шифровании данных на уровне приложения это делает данные бесполезными в базе данных. Вам нужно будет вернуть данные в приложение, чтобы прочитать значения зашифрованных полей. Мы хотим ограничить поля, которые шифруются, и мы хотим шифровать только строковые значения. Шифрование чего-либо, кроме строковых значений, усложняет ситуацию. Ничто не говорит, что мы не можем шифровать даты, но это потребует, чтобы поле даты в базе данных стало полем string (nvarchar или varchar) для хранения зашифрованных данных, и как только мы это сделаем, мы потеряем возможность работать с полем даты из базы данных.
Для определения полей, которые мы хотим зашифровать и расшифровать, я буду использовать маркирующие атрибуты.
Encrypt Attribute
public class EncryptAttribute : Attribute
{
}
Decrypted Attribute
public class DecryptAttribute : Attribute
{
}
Чтобы увидеть EncryptAttribute и DecryptedAttribute в действии, давайте посмотрим на класс 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)
;
}
}
Вот и всё. Теперь шифрование и расшифровка данных будут прозрачны для приложения, и вы сможете продолжить создание следующего Facebook.
Автор: Chuck Conway — инженер AI с почти 30-летним опытом разработки программного обеспечения. Он создает практические системы AI — конвейеры контента, агенты инфраструктуры и инструменты, которые решают реальные проблемы — и делится тем, что он узнает на этом пути. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube и на SubStack.