Skip to content

Посты

Реализация прозрачного шифрования с помощью слушателей NHibernate (перехватчиков)

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

Реализация прозрачного шифрования с помощью слушателей NHibernate (перехватчиков)

Приходилось ли вам когда-нибудь шифровать данные в базе данных? В этом посте я рассмотрю, как использовать слушатели 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.

↑ Наверх

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