Skip to content

文章

使用 NHibernate 监听器(拦截器)实现透明加密

2014年11月3日 • 6 分钟阅读

使用 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

这个列表很广泛。

要实现透明加密,我们需要找到合适的位置来加密和解密数据。对于加密数据,我们将使用 IPostInsertEventListenerIPostUpdateEventListener。通过这些事件,我们将捕获进入数据库的新数据和更新数据。对于解密,我们将使用 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;
}
}

重要的是要注意,IPreUpdateEventListenerIPreInsertEventListener 都必须返回 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)字段来保存加密数据,一旦我们这样做,我们就失去了从数据库操作日期字段的能力。

为了识别我们想要加密和解密的字段,我将使用标记属性。

加密属性

public class EncryptAttribute : Attribute
{
}

解密属性

public class DecryptAttribute : Attribute
{
}

要查看 EncryptAttributeDecryptedAttribute 的实际应用,我们将深入了解 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 专注于软件工程和生成式人工智能。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube

↑ 回到顶部

您可能还喜欢