
データベース内のデータを暗号化する必要に迫られたことはありますか?この記事では、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を返すことが重要です。そうしないと、insert/updateイベントが中止されてしまいます。
リスナーを実装したので、次にそれらを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
{
}
Decrypted属性
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を専門としています。ソーシャルメディアで彼とつながりましょう:X (@chuckconway) または YouTube をご覧ください。