Steven Yates
Steven Yates

Reputation: 2480

EntityFramework storing sensitive data

Say on one of your entities you have a property that when you need saved to the database, it needs to be encrypted but when you're dealing with it in code, you just want to treat is as plain text.

Right now, I have this setup:

public class MyEntity 
{
   [SecureStringAttribute]
   public string SecureString {get;set;}
}

My DbContext, this is where the "magic" happens.

public MyDbContext()
    : base("conn")
{
    ((IObjectContextAdapter)this).ObjectContext.SavingChanges += ObjectContextOnSavingChanges;
    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += ObjectContextOnObjectMaterialized;
}

private void ObjectContextOnObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
{
    DecryptSecureString(e.Entity);
}

private void ObjectContextOnSavingChanges(object sender, EventArgs e)
{
    EncryptSecureStrings(sender as ObjectContext);
}

private void DecryptSecureString(object entity)
{
    if (entity != null)
    {
        foreach (
            PropertyInfo propertyInfo in
                EntityFrameworkSecureStringAttribute.GetSecureStringProperties(entity.GetType()))
        {
            string encryptedValue = propertyInfo.GetValue(entity) as string;
            if (!string.IsNullOrEmpty(encryptedValue))
            {
                string decryptedValue = EncDec.Decrypt(encryptedValue);
                propertyInfo.SetValue(entity, decryptedValue);
            }
        }
    }
}

private void EncryptSecureStrings(ObjectContext context)
{

    if (context != null)
    {
        foreach (ObjectStateEntry objectStateEntry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified).Where(x => x.Entity != null))
        {

            object[] data = new object[objectStateEntry.CurrentValues.FieldCount];
            objectStateEntry.CurrentValues.GetValues(data);

            PropertyInfo[] properties =
                EntityFrameworkSecureStringAttribute.GetSecureStringProperties(objectStateEntry.Entity.GetType());

            foreach (PropertyInfo propertyInfo in properties)
            {
                string currentValue = objectStateEntry.CurrentValues[propertyInfo.Name] as string;
                if (!string.IsNullOrEmpty(currentValue))
                {
                    int index = objectStateEntry.CurrentValues.GetOrdinal(propertyInfo.Name);
                    string newVal = EncDec.Encrypt(currentValue);
                    objectStateEntry.CurrentValues.SetValue(index, newVal);

                }
            }

        }
    }
}

It's straight forward I just encrypt/decrypt the string on save and on load. However if I do the following:

MyEntity entity = new MyEntity(){SecureString= "This is secret!!"};
dbContext.SaveChanges();

At this point entity.SecureString has been encrypted and any further use with this object will be incorrect.

Upvotes: 0

Views: 1482

Answers (3)

Steven Yates
Steven Yates

Reputation: 2480

Thank you both of your suggestions, i have decided to go along with this.... I always seemed to figures stuff out after posting on SO.

I wanted to go for an approach where i don't have to have to worry about adding NotMapped properties etc, although i know this works fine.

Just override the SaveChanges method like this.

public override int SaveChanges()
{
    int result = base.SaveChanges();

    foreach (DbEntityEntry dbEntityEntry in this.ChangeTracker.Entries())
    {
        DecryptSecureString(dbEntityEntry.Entity);
    }
    return result;
}

All this does is after the SaveChanges is called,we go back through and decyryped anyting we need to.

Thanks

Upvotes: 0

Andrew
Andrew

Reputation: 3796

Add a property UnsecuredString and decorate it with [NotMapped] attribute. Implement the getter and setter to decrypt and encrypt the SecureString data.

Upvotes: 2

Keith Payne
Keith Payne

Reputation: 3082

Instead of one single property that will be flipped to/from being encrypted, you can do it with a pair of properties - one that is always encrypted, and one that is never encrypted.

public class MyModel
{
    public string EncryptedInfo { get; set; }
    public string PlainTextInfo { 
        get
        {
            return Decrypt(EncryptedInfo);
        }
        set
        {
            EncryptedInfo = Encrypt(value);
        }
}

And in the model builder, ignore the unencrypted property:

public class MyDbContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
         modelBuilder.Entity<MyModel>()
            .Ignore(m => m.PlainTextInfo);
    }
}

As long as you don't mess with the encrypted property elsewhere in your application, it should work well.

Upvotes: 3

Related Questions