具有保存加密数据的属性的EF Code First模型

时间:2017-11-10 02:24:12

标签: entity-framework entity-framework-6

我在数据库中的表中加了一些加密代码,我正在维护。这是大约2012年,所以没有"永远在"加密。 3列包含加密数据。

如果我对EF域进行反向工程,则为该表创建的模型包含那些类型为byte[]的列的属性。这是预期的,因为列是varbinary。所以,它看起来像这样:

class Person 
{
  public byte[] FirstName { get; set; } // FirstName
}

是否有一种优雅的方式来进行某种EF映射/配置,以便FirstName类的类型为string并且它可以通过框架自动解密?我意识到我可以使用sql实例化一个Person对象,但将这个处理卸载到框架会很不错。

我已经看到两种解决方案中的一种,人们基本上都在为每个属性使用sql查询。它们使用Encrypt属性修饰属性并迭代每个属性的属性。但是对于列表中的每个对象的每个属性都有一个sql查询 - 这不完全可以扩展。

有没有人"解决了#34;这个问题之前?

注意:要检索数据,首先需要发送类似于:

的sql语句
OPEN SYMMETRIC KEY SomeKey DECRYPTION BY CERTIFICATE SomeCertificate

由于

1 个答案:

答案 0 :(得分:2)

在这个答案中,我将列出在EF中处理加密列所需要做的事情。因此,相关列的类型为VARBINARY(MAX)。让我们说你的表看起来像这样:

CREATE TABLE dbo.Person
(
    SomeId int NOT NULL,
    CreatedByUserId uniqueidentifier NULL,
    CreatedUtcDate datetimeoffset(7) NULL,
    Rowversion timestamp NULL,
    FirstName varbinary(MAX) NULL,
    LastName varbinary(MAX) NULL
)

第1步 - 创建一个返回解密列的视图。视图应基本上与您的表相同,但对于保存加密数据的列,它应返回解密的数据。它看起来像这样:

CREATE VIEW [dbo].[v_Person]
AS
SELECT [SomeId]
      ,[CreatedByUserId]
      ,[CreatedUtcDate]
      ,[RowVersion]
      ,CONVERT(NVARCHAR(50),DECRYPTBYKEY([FirstName])) [FirstName]
      ,CONVERT(NVARCHAR(50),DECRYPTBYKEY([LastName])) [LastName]
FROM [dbo].[Person]

第2步 - 创建域模型Person类,其中string为相关属性类型,而不是byte[](请注意select中的View语句1}}我们将解密的列转换为NVARCHAR)。

public class Person 
{
    public int SomeId { get; set; }
    public string FirstName { get; set; } // string, not binary
    public string LastName { get; set; } // string, not binary
    public Guid CreatedByUserId { get; set; } 
    public DateTime CreatedUtcDate { get; set; }      
    public int SomeForeignKeyId { get; set; }
}

第3步 - 我们需要为该Domain类设置映射。 (我在这里提出的解决方案是针对EF6。我知道EF Core还不支持单独的映射文件,所以这需要在DbContext的OnModelCreating事件中完成)。为您的域对象创建一个映射类,如下所示:

public class PersonMap : EntityTypeConfiguration<Person>
{
    public PersonConfiguration(string schema)
    {
        ToTable("v_Person", schema); // note we map to the View
        HasKey(x => x.SomeId);  

        // ... other properties elided for brevity

        Property(x => x.FirstName)
            .HasColumnName(@"FirstName")
            .HasColumnType("nvarchar")
            .IsOptional()
            .HasMaxLength(50);
        Property(x => x.LastName)
            .HasColumnName(@"LastName")
            .HasColumnType("nvarchar")
            .IsOptional()
            .HasMaxLength(50);

        // Foreign keys
        HasRequired(a => a.LogbookEntry)
            .WithOptional(b => b.Person)
            .WillCascadeOnDelete(false);

        MapToStoredProcedures(p =>
            p.Insert(i => i.HasName("Insert_Person"))
                .Update(u => u.HasName("Update_Person"))
                .Delete(d => d.HasName("Delete_Person")));

    }
}

注意我们如何映射到视图v_Person,而不是原始表 另请注意MapToStoredProcedures的来电,我将在下面解释。

第4步 - 最后一步是为Insert,Update和Deletes创建一些存储过程。当您调用SaveChanges时,这些将由EF调用,并且将根据实体具有的EntityState调用相关的存储过程。我不会列出所有3,但Update存储过程的示例可能类似于:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Update_Person]
    @SomeId INT,
    @CreatedByUserId UNIQUEIDENTIFIER,
    @CreatedUtcDate DATETIME,
    @RowVersion_Original timestamp,
    @FirstName NVARCHAR(50),
    @LastName NVARCHAR(50) = NULL   
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @CertKey NVARCHAR(7) = 'CertKey';

    UPDATE PersonDetail
        SET             
            FirstName = ENCRYPTBYKEY(KEY_GUID(@CertKey), @FirstName),
            LastName = ENCRYPTBYKEY(KEY_GUID(@CertKey), @LastName)          
    WHERE SomeId = @SomeId

    SELECT SomeId, RowVersion
    FROM PersonDetail
    WHERE SomeId = @SomeId
END

如果你做得更好,请随意发表评论 干杯