您是否曾经需要对数据库中的数据进行加密?在本文中,我将探讨如何使用 nHibernate 监听器来加密和解密来自数据库和进入数据库的数据。加密过程对您的应用程序是透明的。
为什么要这样做?SQL Server 在产品中内置了加密功能。这是真的,但如果您要迁移到云端并想使用 SQL Azure,您将需要某种加密策略。SQL Azure 不支持数据库加密。
什么是 nHibernate 监听器?我认为监听器是一段代码,我可以将其注入到 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,否则插入/更新事件将被中止。
现在我们已经实现了监听器,我们需要将它们注册到 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
{
}
Decrypt 属性
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。