سؤال

I have a requirement to store some information in the database encrypted (not hashed, and I'm not talking about passwords here), but still be able to read that information in memory to validate certain scenarios (hence the encrypting, not hashing).

I want a clean easy way to identify which columns should be stored as encrypted in the database because they may change in the future. With that said, I was wondering if the following approach would work with the ApplicationDbContext provided by IdentityProvider in MVC5 and Entity Framework 6. Would there be any caveats to watch out for? Or is this even a good approach to the Idea? If not, any guidance would be appreciated.

  • To define columns (code first) I created an attribute called "StoreSecurelyAttribute" that can be applied to different properties in the Code-First model.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class StoreSecurelyAttribute : Attribute { }
  • I can then apply this attribute to any column that needs to be stored securely (only strings supported for now).
public class UserProfileInfo
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    [Required, StoreSecurely]
    public string SomePersonalInformation { get; set; }
}
  • Then in my ApplicationDbContext Constructor I add the following:
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 ...
}

Update: I know this is a lot of reflection and could be slow -- but we're not going to get too crazy on encrypting and decrypting fields and the ones we do encrypt, are not hit "on every request."

هل كانت مفيدة؟

المحلول

I would make the read/write operations on the protected data deliberate.

Also, I don't know if you need to read the data from the same place it's written, but if not then consider asymmetric encryption for protecting the data. This way the place where the data's written (such as a web app) only needs access to the private key, and the elsewhere in your system where the data's being read then only it needs access to the public key.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top