Skip to content

Посты

Реализация прозрачного шифрования с помощью NHibernate Listeners (Interceptors)

3 ноября 2014 г. • 4 мин чтения

Реализация прозрачного шифрования с помощью NHibernate Listeners (Interceptors)

Приходилось ли вам когда-нибудь шифровать данные в базе данных? В этой статье я рассмотрю, как использовать nHibernate Listeners для шифрования и расшифровки данных, поступающих из базы данных и записываемых в неё. Криптография будет прозрачна для вашего приложения.

Зачем это нужно? SQL Server имеет встроенное шифрование. Это правда, но если вы переходите в облако и хотите использовать SQL Azure, вам потребуется какая-то стратегия криптографии. SQL Azure не поддерживает шифрование базы данных.

Что такое nHibernate Listener? Я думаю о Listener как о коде, который я могу внедрить в определённые точки расширяемости в жизненном цикле сохранения и гидратации данных 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, иначе событие 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.

↑ Вернуться в начало

Вам также может понравиться