Articles
Implémentation du chiffrement transparent avec les écouteurs NHibernate (Intercepteurs)
3 novembre 2014 • 5 min de lecture

Avez-vous déjà eu besoin de chiffrer des données dans la base de données ? Dans cet article, j’explore comment utiliser les écouteurs nHibernate pour chiffrer et déchiffrer les données provenant de et allant vers votre base de données. La cryptographie sera transparente pour votre application.
Pourquoi voudriez-vous faire cela ? SQL Server a le chiffrement intégré dans le produit. C’est vrai, mais si vous migrez vers le cloud et voulez utiliser SQL Azure, vous aurez besoin d’une stratégie de cryptographie. SQL Azure ne prend pas en charge le chiffrement de base de données.
Qu’est-ce qu’un écouteur nHibernate ? Je considère un écouteur comme un morceau de code que je peux injecter dans des points d’extensibilité spécifiques du cycle de vie de persistance et d’hydratation des données de nHibernate.
Au moment de la rédaction de cet article, les points d’extensibilité suivants sont disponibles dans 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
La liste est exhaustive.
Pour implémenter la cryptographie transparente, nous devons trouver le bon endroit pour chiffrer et déchiffrer les données. Pour chiffrer les données, nous utiliserons IPostInsertEventListener
et IPostUpdateEventListener
. Avec ces événements, nous intercepterons les nouvelles données et les données mises à jour allant dans la base de données. Pour déchiffrer, nous utiliserons le IPreLoadEventListener
.
Pour cette démonstration, nous utiliserons la classe DatabaseCryptography
pour chiffrer et déchiffrer. L’implémentation de la cryptographie n’est pas importante pour cet article.
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;
}
}
Il est important de noter que IPreUpdateEventListener
et IPreInsertEventListener
doivent tous deux retourner false, sinon l’événement d’insertion/mise à jour sera annulé.
Maintenant que nous avons implémenté les écouteurs, nous devons les enregistrer avec nHibernate. J’utilise FluentNHibernate
donc ce sera différent si vous utilisez nHibernate brut.
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();
}
Lorsqu’on déchiffre et chiffre les données au niveau de l’application, cela rend les données inutiles dans la base de données. Vous devrez ramener les données dans l’application pour lire les valeurs des champs chiffrés. Nous voulons limiter les champs qui sont chiffrés et nous ne voulons chiffrer que les valeurs de chaîne. Chiffrer autre chose que des valeurs de chaîne complique les choses. Rien ne dit que nous ne pouvons pas chiffrer les dates, mais le faire nécessitera que le champ de date dans la base de données devienne un champ de chaîne (nvarchar ou varchar), pour contenir les données chiffrées, une fois que nous faisons cela, nous perdons la capacité d’opérer sur le champ de date depuis la base de données.
Pour identifier quels champs nous voulons chiffrés et déchiffrés, j’utiliserai des attributs marqueurs.
Attribut Encrypt
public class EncryptAttribute : Attribute
{
}
Attribut Decrypted
public class DecryptAttribute : Attribute
{
}
Pour voir le EncryptAttribute
et le DecryptedAttribute
en action, nous jetterons un coup d’œil dans la classe 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)
;
}
}
C’est tout. Maintenant le chiffrement et le déchiffrement des données seront transparents pour l’application et vous pouvez continuer votre chemin en construisant le prochain Facebook.
Auteur : Chuck Conway se spécialise dans l’ingénierie logicielle et l’IA générative. Connectez-vous avec lui sur les réseaux sociaux : X (@chuckconway) ou visitez-le sur YouTube.