Посты
Реализация прозрачного шифрования с помощью слушателей NHibernate (перехватчиков)
3 ноября 2014 г. • 4 мин чтения

Приходилось ли вам когда-нибудь шифровать данные в базе данных? В этом посте я рассмотрю, как использовать слушатели nHibernate для шифрования и расшифровки данных, поступающих в базу данных и извлекаемых из неё. Криптография будет прозрачной для вашего приложения.
Зачем это может понадобиться? В SQL Server есть встроенное шифрование. Это правда, но если вы переходите в облако и хотите использовать SQL Azure, вам понадобится какая-то стратегия криптографии. SQL Azure не поддерживает шифрование базы данных.
Что такое слушатель nHibernate? Я думаю о слушателе как о части кода, которую я могу внедрить в определённые точки расширения в жизненном цикле сохранения и гидратации данных nHibernate.
На момент написания этой статьи в 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
Список обширный.
Для реализации прозрачной криптографии нам нужно найти правильное место для шифрования и расшифровки данных. Для шифрования данных мы будем использовать 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, иначе событие вставки/обновления будет прервано.
Теперь, когда у нас есть реализованные слушатели, нам нужно зарегистрировать их в nHibernate. Я использую FluentNHibernate
, поэтому это будет отличаться, если вы используете чистый 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();
}
Когда мы расшифровываем и шифруем данные на уровне приложения, это делает данные бесполезными в базе данных. Вам нужно будет вернуть данные обратно в приложение, чтобы прочитать значения зашифрованных полей. Мы хотим ограничить поля, которые шифруются, и мы хотим шифровать только строковые значения. Шифрование чего-либо, кроме строковых значений, усложняет дело. Ничто не говорит о том, что мы не можем шифровать даты, но это потребует, чтобы поле даты в базе данных стало строковым полем (nvarchar или varchar) для хранения зашифрованных данных, и как только мы это сделаем, мы потеряем возможность работать с полем даты из базы данных.
Для определения полей, которые мы хотим зашифровать и расшифровать, я буду использовать атрибуты-маркеры.
Атрибут Encrypt
public class EncryptAttribute : Attribute
{
}
Атрибут Decrypted
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.
Автор: Чак Конвей специализируется на разработке программного обеспечения и генеративном ИИ. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube.