Beiträge
Implementierung transparenter Verschlüsselung mit NHibernate Listeners (Interceptors)
3. November 2014 • 5 Min. Lesezeit

Mussten Sie schon einmal Daten in der Datenbank verschlüsseln? In diesem Beitrag erkunde ich, wie man nHibernate Listeners verwendet, um Daten zu verschlüsseln und zu entschlüsseln, die von und in Ihre Datenbank gelangen. Die Kryptographie wird für Ihre Anwendung transparent sein.
Warum sollten Sie das tun wollen? SQL Server hat Verschlüsselung in das Produkt eingebaut. Das ist wahr, aber wenn Sie in die Cloud wechseln und SQL Azure verwenden möchten, benötigen Sie eine Art Kryptographie-Strategie. SQL Azure unterstützt keine Datenbankverschlüsselung.
Was ist ein nHibernate Listener? Ich denke an einen Listener als ein Stück Code, das ich in spezifische Erweiterungspunkte im nHibernate-Persistierungs- und Datenhydratations-Lebenszyklus einschleusen kann.
Zum Zeitpunkt dieses Artikels sind die folgenden Erweiterungspunkte in nHibernate verfügbar.
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
Die Liste ist umfangreich.
Um transparente Kryptographie zu implementieren, müssen wir den richtigen Ort finden, um die Daten zu verschlüsseln und zu entschlüsseln. Für die Verschlüsselung der Daten verwenden wir IPostInsertEventListener
und IPostUpdateEventListener
. Mit diesen Events fangen wir die neuen Daten und die aktualisierten Daten ab, die in die Datenbank gelangen. Für die Entschlüsselung verwenden wir den IPreLoadEventListener
.
Für diese Demonstration verwenden wir die DatabaseCryptography
-Klasse für die Verschlüsselung und Entschlüsselung. Die Kryptographie-Implementierung ist für diesen Artikel nicht wichtig.
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;
}
}
Es ist wichtig zu beachten, dass sowohl IPreUpdateEventListener
als auch IPreInsertEventListener
false zurückgeben müssen, andernfalls wird das Insert/Update-Event abgebrochen.
Jetzt, da wir die Listeners implementiert haben, müssen wir sie bei nHibernate registrieren. Ich verwende FluentNHibernate
, daher wird dies anders sein, wenn Sie rohes nHibernate verwenden.
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();
}
Wenn Daten auf Anwendungsebene entschlüsselt und verschlüsselt werden, macht das die Daten in der Datenbank nutzlos. Sie müssen die Daten zurück in die Anwendung bringen, um die Werte der verschlüsselten Felder zu lesen. Wir möchten die Felder begrenzen, die verschlüsselt werden, und wir möchten nur String-Werte verschlüsseln. Die Verschlüsselung von etwas anderem als String-Werten verkompliziert die Dinge. Es gibt nichts, was sagt, dass wir keine Daten verschlüsseln können, aber dies erfordert, dass das Datumsfeld in der Datenbank zu einem String-Feld (nvarchar oder varchar) wird, um die verschlüsselten Daten zu halten. Sobald wir das tun, verlieren wir die Fähigkeit, auf dem Datumsfeld von der Datenbank aus zu operieren.
Um zu identifizieren, welche Felder wir verschlüsselt und entschlüsselt haben möchten, verwende ich Marker-Attribute.
Encrypt Attribute
public class EncryptAttribute : Attribute
{
}
Decrypted Attribute
public class DecryptAttribute : Attribute
{
}
Um das EncryptAttribute
und das DecryptedAttribute
in Aktion zu sehen, werfen wir einen Blick in die DatabaseCryptography
-Klasse.
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)
;
}
}
Das war’s. Jetzt wird die Verschlüsselung und Entschlüsselung von Daten für die Anwendung transparent sein und Sie können fröhlich weitermachen und das nächste Facebook entwickeln.
Autor: Chuck Conway ist spezialisiert auf Software-Engineering und Generative KI. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube.