Innlegg
Implementering av transparent kryptering med NHibernate Listeners (Interceptors)
3. november 2014 • 5 min lesing

Har du noen gang måttet kryptere data i databasen? I dette innlegget vil jeg utforske hvordan man bruker nHibernate Listeners for å kryptere og dekryptere data som kommer fra og går inn i databasen din. Kryptografien vil være transparent for applikasjonen din.
Hvorfor ville du gjøre dette? SQL Server har kryptering innebygd i produktet. Det er sant, men hvis du flytter til skyen og ønsker å bruke SQL Azure trenger du en slags kryptografistrategi. SQL Azure støtter ikke databasekryptering.
Hva er en nHibernate Listener? Jeg tenker på en Listener som et stykke kode som jeg kan injisere inn i spesifikke utvidelsespunkter i nHibernate persistering og datahydrering livssyklus.
Per denne skrivingen er følgende utvidelsespunkter tilgjengelige i 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
Listen er omfattende.
For å implementere transparent kryptografi må vi finne det rette stedet å kryptere og dekryptere dataene. For å kryptere dataene vil vi bruke IPostInsertEventListener
og IPostUpdateEventListener
. Med disse hendelsene vil vi fange opp nye data og oppdaterte data som går inn i databasen. For dekryptering vil vi bruke IPreLoadEventListener
.
For denne demonstrasjonen vil vi bruke DatabaseCryptography
klassen for kryptering og dekryptering. Kryptografiimplementeringen er ikke viktig for denne artikkelen.
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;
}
}
Det er viktig å merke seg at både IPreUpdateEventListener
og IPreInsertEventListener
må returnere false, ellers vil insert/update hendelsen bli avbrutt.
Nå som vi har implementert Listeners må vi registrere dem med nHibernate. Jeg bruker FluentNHibernate
så dette vil være annerledes hvis du bruker rå 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();
}
Når man dekrypterer og krypterer data på applikasjonsnivå gjør det dataene ubrukelige i databasen. Du må bringe dataene tilbake inn i applikasjonen for å lese verdiene til de krypterte feltene. Vi ønsker å begrense feltene som krypteres og vi ønsker bare å kryptere strengverdier. Å kryptere noe annet enn strengverdier kompliserer ting. Det er ingenting som sier at vi ikke kan kryptere datoer, men å gjøre det vil kreve at datofelt i databasen blir et streng(nvarchar eller varchar) felt, for å holde de krypterte dataene, når vi gjør dette mister vi muligheten til å operere på datofeltet fra databasen.
For å identifisere hvilke felt vi ønsker kryptert og dekryptert vil jeg bruke markørattributter.
Encrypt Attribute
public class EncryptAttribute : Attribute
{
}
Decrypted Attribute
public class DecryptAttribute : Attribute
{
}
For å se EncryptAttribute
og DecryptedAttribute
i aksjon vil vi ta en titt inn i DatabaseCryptography
klassen.
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)
;
}
}
Det var det. Nå vil kryptering og dekryptering av data være transparent for applikasjonen og du kan fortsette på din muntre vei med å bygge neste Facebook.
Forfatter: Chuck Conway spesialiserer seg på programvareutvikling og Generativ AI. Koble til ham på sosiale medier: X (@chuckconway) eller besøk ham på YouTube.