使用单独的类通过实体框架加密解密数据 公开加密/解密的值:Linq 语句失败

本文关键字:解密 加密 失败 语句 Linq 数据 单独 加密解密 框架 实体 | 更新日期: 2023-09-27 18:35:39

除了我之前关于实体框架的问题。我的目的是在保存到数据库之前加密字段,并在从数据库读取之前解密字段。我有从表用户(具有用户名属性(要加密))生成的类(用户.cs),并且我创建了一个单独的类 SecureUser,该类具有用户名属性,如前所述进行加密/解密。但是我不确定如何将这个新的 SecureUser 类映射到数据库而不是以前的 POCO 类用户。当我将用户名替换为 SecureUser 类中的用户名时,我的 Linq 查询失败。我尝试对部分类做同样的事情,同样的事情发生了。任何建议将不胜感激!

[Table("Users")]
public class User
{
    #region database table column mapped fields
    [Key]
    [Required]
    public Int32 UserID { set; get; }
    [Required]
    [MaxLength(50)]
    public String UserName { set; get; }
    [Required]
    public Int32 CustID { set; get; }
//created the separate class as
public class SecureUser // UserViewModel
{
    // 
    private readonly User _user;
   public SecureUser(User user)
    {
        _user = user;
    }
    public string UserName
    {
        get { return Decrypt(_user.UserName); }
        set { _user.UserName = Encrypt(value); }
    }

}

使用单独的类通过实体框架加密解密数据 公开加密/解密的值:Linq 语句失败

我是任何依赖于属性的解决方案的忠实粉丝。

假设您有一个实体类,其中包含一个或多个要存储在数据库中加密的属性。只需添加[Encrypted]属性即可。

[Encrypted] 
public string EncryptedProperty { get; set; }

现在创建一个自定义 DbContext,用于动态执行加密/解密:

public class MyDB : IdentityDbContext<User>
{  
    //DBSet properties go here
    public MyDB()
    {
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += new ObjectMaterializedEventHandler(ObjectMaterialized);
    }
    #region Encryption
    public override int SaveChanges()
    {
        var contextAdapter = ((IObjectContextAdapter)this);
        contextAdapter.ObjectContext.DetectChanges(); //force this. Sometimes entity state needs a handle jiggle
        var pendingEntities = contextAdapter.ObjectContext.ObjectStateManager
            .GetObjectStateEntries(EntityState.Added | EntityState.Modified)
            .Where(en => !en.IsRelationship).ToList();
        foreach (var entry in pendingEntities) //Encrypt all pending changes
            EncryptEntity(entry.Entity);
        int result = base.SaveChanges();
        foreach (var entry in pendingEntities) //Decrypt updated entities for continued use
            DecryptEntity(entry.Entity);
        return result;
    }
    public override async Task<int> SaveChangesAsync(System.Threading.CancellationToken cancellationToken)
    {
        var contextAdapter = ((IObjectContextAdapter)this);
        contextAdapter.ObjectContext.DetectChanges(); //force this. Sometimes entity state needs a handle jiggle
        var pendingEntities = contextAdapter.ObjectContext.ObjectStateManager
            .GetObjectStateEntries(EntityState.Added | EntityState.Modified)
            .Where(en => !en.IsRelationship).ToList();
        foreach (var entry in pendingEntities) //Encrypt all pending changes
            EncryptEntity(entry.Entity);
        var result = await base.SaveChangesAsync(cancellationToken);
        foreach (var entry in pendingEntities) //Decrypt updated entities for continued use
            DecryptEntity(entry.Entity);
        return result;
    }
    void ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
    {
        DecryptEntity(e.Entity);
    }
    private void EncryptEntity(object entity)
    {
        //Get all the properties that are encryptable and encrypt them
        var encryptedProperties = entity.GetType().GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(Encrypted), true).Any(a => p.PropertyType == typeof(String)));
        foreach (var property in encryptedProperties)
        {
            string value = property.GetValue(entity) as string;
            if (!String.IsNullOrEmpty(value))
            {
                string encryptedValue = EncryptionService.Encrypt(value);
                property.SetValue(entity, encryptedValue);
            }
        }
    }
    private void DecryptEntity(object entity)
    {
        //Get all the properties that are encryptable and decyrpt them
        var encryptedProperties = entity.GetType().GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(Encrypted), true).Any(a => p.PropertyType == typeof(String)));
        foreach (var property in encryptedProperties)
        {
            string encryptedValue = property.GetValue(entity) as string;
            if (!String.IsNullOrEmpty(encryptedValue))
            {
                string value = EncryptionService.Decrypt(encryptedValue);
                this.Entry(entity).Property(property.Name).OriginalValue = value;
                this.Entry(entity).Property(property.Name).IsModified = false;
            }
        }
    }
    #endregion Encryption
}

然后添加类

class Encrypted : Attribute
{
}

来源: https://gist.github.com/albertbori/e95860644e69c1572441

不能在 LINQ

to Entities 查询中使用SecureUserUserName 属性,因为它调用的是 Decrypt 方法,而 LINQ 不知道如何将其转换为 SQL。您可能应该只在常规User对象上创建一个 [NotMapped] 属性,这应该可以做到。这样:

[Table("Users")]
public class User
{
  #region database table column mapped fields
  [Key]
  [Required]
  public Int32 UserID { set; get; }
  [Required]
  [MaxLength(50)]
  public String UserName { set; get; }
  [Required]
  public Int32 CustID { set; get; }
  [NotMapped]
  public string DecryptedUserName
  {
    get { return Decrypt(this.UserName); }
    set { this.UserName = Encrypt(value); }
  }
}

如果这不起作用,则必须在对象从 LINQ to Objects 查询中的数据库返回后执行解密。像这样:

var users = db.Users.Where(u => u.CustID == someID); //LINQ to Entities
var decryptedUserNames = users.ToList().Select(u => Decrypt(u.UserName)); //LINQ to Objects

在将用户对象添加到数据库上下文时,您应该设置 UserName=Encrypt(model.用户名),其中模型是用户返回的用于添加它的对象

// before adding the model to the dbcontext
model.UserName=Encrypt(mode.UserName);
db.Users.Add(model);
db.SaveChanges();
[Table("Users")]
public class User
{
    #region database table column mapped fields
    [Key]
    [Required]
    public Int32 UserID { set; get; }
    [Required]
    [MaxLength(50)]
    public String UserName { set; get; } // this field will be encrypted field
    [Required]
    public Int32 CustID { set; get; }
    [NotMapped]
    public string DecryptedUserName
    {
        get { return Decrypt(UserName); }  // this you can display it to the user
    }
}

要使用 NotMapped,您应该在 using 语句中添加此行

using System.ComponentModel.DataAnnotations.Schema;

希望这对你有帮助

您还可以使用免费的Crypteron CipherDb库,该库可以透明地处理加密数据。还有一个功能允许在 LINQ 查询中使用可搜索加密。

您可以使用[Secure]批注数据模型或将属性命名为类似 Secure_SocialSecurityNumberSecure_是密钥部分),CipherDb 会自动执行数据加密、篡改保护、安全密钥存储、安全密钥分发、缓存、密钥滚动更新、ACL 等。或者,使用密码对象适配器,您可以只使用myObject.Seal()来加密对象。

您可以在 GitHub 上找到示例应用,请访问 https://github.com/crypteron/crypteron-sample-apps。您还可以使用Cryptron来保护流,文件,对象,消息队列,noSQL等。

免责声明:我在那里工作,我们确实有一个任何人都可以使用的免费社区版本。