Безопасное хранение данных столбца с помощью EF6 и Identity Provider
У меня есть требование хранить некоторую информацию в базе данных в зашифрованном виде (не хэшировать, и я не говорю о паролях здесь), но все же иметь возможность читать эту информацию в памяти для проверки определенных сценариев (отсюда шифрование, а не хеширование).
Я хочу простой и понятный способ определить, какие столбцы должны храниться в зашифрованном виде в базе данных, поскольку они могут измениться в будущем. С учетом сказанного мне было интересно, будет ли следующий подход работать с ApplicationDbContext, предоставленным IdentityProvider в MVC5 и Entity Framework 6. Будут ли какие-то предостережения, на которые стоит обратить внимание? Или это даже хороший подход к идее? Если нет, то любые рекомендации будут оценены.
- Чтобы определить столбцы (сначала код), я создал атрибут "StoreSecurelyAttribute", который можно применять к различным свойствам в модели Code-First.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class StoreSecurelyAttribute : Attribute { }
- Затем я могу применить этот атрибут к любому столбцу, который необходимо надежно хранить (пока поддерживаются только строки).
public class UserProfileInfo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required, StoreSecurely]
public string SomePersonalInformation { get; set; }
}
- Затем в моем конструкторе ApplicationDbContext я добавляю следующее:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection", false)
{
var ctx = ((IObjectContextAdapter) this).ObjectContext;
ctx.ObjectMaterialized += OnObjectMaterialized;
ctx.SavingChanges += OnSavingChanges;
}
void OnSavingChanges(object sender, EventArgs e)
{
foreach (var entry in ((ObjectContext) sender).ObjectStateManager.GetObjectStateEntries(EntityState.Added |
EntityState.Modified))
{
foreach (var propInfo in entry.Entity.GetType().GetProperties()
.Where(prop => prop.PropertyType == typeof(string) && Attribute.IsDefined(prop, typeof(StoreSecurelyAttribute))))
{
var plainTextValue = propInfo.GetValue(entry.Entity) as string;
// TODO: encrypt using injected encryption provider
var encryptedValue = Encrypt(plainTextValue);
propInfo.SetValue(entry.Entity, encryptedValue);
}
}
}
void OnObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
{
foreach (var propInfo in e.Entity.GetType().GetProperties()
.Where(prop => prop.PropertyType == typeof(string) && Attribute.IsDefined(prop, typeof(StoreSecurelyAttribute))))
{
var encryptedValue = propInfo.GetValue(e.Entity) as string;
// TODO: decrypt using injected encryption provider
var plainTextValue = Decrypt(encryptedValue);
propInfo.SetValue(e.Entity, plainTextValue);
}
}
public override int SaveChanges()
{
// Hold onto them before their state changes and they're no longer "added" or "modified"
var entries = ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).ToList();
// Go Ahead and save...
var result = base.SaveChanges();
// After saving to db, we want our local hydrated object to be "correct" so... decrypt...
foreach (var entry in entries)
{
foreach (var propInfo in entry.Entity.GetType().GetProperties()
.Where(prop => prop.PropertyType == typeof(string) && Attribute.IsDefined(prop, typeof(StoreSecurelyAttribute))))
{
var encryptedValue = propInfo.GetValue(entry.Entity) as string;
var plainTextValue = Decrypt(encryptedValue);
propInfo.SetValue(entry.Entity, plainTextValue);
}
}
return result;
}
// ... snip ...
}
Обновление: я знаю, что это много размышлений и может быть медленным - но мы не собираемся слишком увлекаться шифрованием и дешифрованием полей, а те, которые мы шифруем, не попадают "под каждый запрос".
1 ответ
Я сделал бы операции чтения / записи на защищенных данных преднамеренно.
Кроме того, я не знаю, нужно ли вам читать данные из того же места, где они написаны, но если нет, то рассмотрите возможность асимметричного шифрования для защиты данных. Таким образом, место, где записываются данные (например, веб-приложение), нуждается только в доступе к закрытому ключу, а в другом месте вашей системы, где данные читаются, только доступ к открытому ключу.