
您是否曾经需要在数据库中加密数据?在这篇文章中,我将探讨如何使用 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)字段来保存加密数据,一旦我们这样做,我们就失去了从数据库操作日期字段的能力。
为了识别我们想要加密和解密的字段,我将使用标记属性。
加密属性
public class EncryptAttribute : 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 专注于软件工程和生成式人工智能。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube。