在EF Core中为数据表按列加密存储

Posted 林晓lx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在EF Core中为数据表按列加密存储相关的知识,希望对你有一定的参考价值。

假设有User表

public class User : Entity<int>

    public int Id  get; set; 
    public string UserName  get; set; 
    public string Name  get; set; 
    public string IdentificationNumber  get; set; 

其中有身份证号码IdentificationNumber列,需要加密存储,该如何实现?
![在这里插入图片描述](https://img-blog.csdnimg.cn/a9edb9793cc841ff91391ec7d4d298d7.png

创建一个值转换器,继承ValueConverter<TModel, string>类型。其中泛型TModel为实体中属性的类型。

转换器将实体中属性类型,通过AES加密算法,转换为Base64编码字符串类型,存储到数据库中。当从数据库中读取数据时,再通过AES解密算法,将Base64编码字符串类型转换为实体中属性类型。

若实体类型为byte[],则不需要转换为Base64编码字符串类型,直接对二进制数据进行加密和解密。此转换器可以用于加密存储图片、文件等二进制数据。

AES加密算法是一种对称加密算法,加密和解密使用相同的密钥。在加密和解密时,需要指定密钥、初始向量、盐值等参数。在转换器中,将这些参数设置为静态属性,方便在使用时,进行修改。

代码如下:

public class EncryptionConverter<TModel> : ValueConverter<TModel, string>


    public const int DefaultKeysize = 256;

    public static string DefaultPassPhrase  get; set; 

    public static byte[] DefaultInitVectorBytes  get; set; 

    public static byte[] DefaultSalt  get; set; 
    public EncryptionConverter()
        : base(
            x => Encrypt(x),
            x => Decrypt(x))
    
        DefaultPassPhrase = "gsKnGZ041HLL4IM8";
        DefaultInitVectorBytes = Encoding.ASCII.GetBytes("jkE49230Tf093b42");
        DefaultSalt = Encoding.ASCII.GetBytes("hgt!16kl");
    

    private static string Encrypt(TModel input)
    
        try
        
            byte[] inputData = input switch
            
                string => Encoding.UTF8.GetBytes(input.ToString()),
                byte[] => input as byte[],
                _ => null,
            ;

            using (var password = new Rfc2898DeriveBytes(DefaultPassPhrase, DefaultSalt))
            
                var keyBytes = password.GetBytes(DefaultKeysize / 8);
                using (var symmetricKey = Aes.Create())
                
                    symmetricKey.Mode = CipherMode.CBC;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, DefaultInitVectorBytes))
                    
                        using (var memoryStream = new MemoryStream())
                        
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            
                                cryptoStream.Write(inputData, 0, inputData.Length);
                                cryptoStream.FlushFinalBlock();
                                var cipherTextBytes = memoryStream.ToArray();
                                var rawString = Convert.ToBase64String(cipherTextBytes);
                                return rawString;

                            
                        
                    
                
            
        
        catch (Exception ex)
                      
            LogHelper.LogException(ex);
            return input.ToString();              
        
        
    

    private static TModel Decrypt(string input)
    
        try
        
            var cipherTextBytes = Convert.FromBase64String(input);

            using (var password = new Rfc2898DeriveBytes(DefaultPassPhrase, DefaultSalt))
            
                var keyBytes = password.GetBytes(DefaultKeysize / 8);
                using (var symmetricKey = Aes.Create())
                
                    symmetricKey.Mode = CipherMode.CBC;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, DefaultInitVectorBytes))
                    
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                int totalDecryptedByteCount = 0;
                                while (totalDecryptedByteCount < plainTextBytes.Length)
                                
                                    var decryptedByteCount = cryptoStream.Read(
                                        plainTextBytes,
                                        totalDecryptedByteCount,
                                        plainTextBytes.Length - totalDecryptedByteCount
                                    );

                                    if (decryptedByteCount == 0)
                                    
                                        break;
                                    

                                    totalDecryptedByteCount += decryptedByteCount;
                                
                                byte[] outputData = null;
                                if (typeof(TModel) == typeof(string))
                                
                                    outputData = Encoding.UTF8.GetBytes(plainTextBytes.ToString());
                                
                                else if (typeof(TModel) == typeof(byte[]))
                                
                                    outputData = plainTextBytes as byte[];
                                ;

                                var rawString = Encoding.UTF8.GetString(outputData, 0, totalDecryptedByteCount);

                                return (TModel)Convert.ChangeType(rawString, typeof(TModel));

                            
                        
                    
                
            

        
        catch (Exception ex)
        
        	// 记录异常
            // LogHelper.LogException(ex);
            return (TModel)Convert.ChangeType(input, typeof(TModel));
        

    


DbContext中,重写OnModelCreating方法,为User表的IdentificationNumber列,添加值转换器。

protected override void OnModelCreating(ModelBuilder modelBuilder)

    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<User>().Property(c => c.IdentificationNumber).HasConversion<EncryptionConverter<string>>();


再次调用Add方法插入数据时,可以看到IdentificationNumber列已被加密了

ASP MVC 应用程序中的列加密与使用 .net Core / EF Core 的 SQL Server 2016

【中文标题】ASP MVC 应用程序中的列加密与使用 .net Core / EF Core 的 SQL Server 2016【英文标题】:Column encryption in ASP MVC app with SQL Server 2016 using .net Core / EF Core 【发布时间】:2017-05-01 17:23:22 【问题描述】:

我正在尝试使用 SQL Server 2016 中的“始终加密”功能来加密某些列。我使用this post 作为指导,将列设置为在 SSDT 中加密。

这部分很好,当我尝试从应用程序中查询数据时,我得到了一个错误。根据我需要添加的文档:

column encryption setting=enabled

到我的连接字符串。 Entity Framework Core 似乎不支持此功能。我收到此错误:

不支持列加密设置=启用

我也尝试使用SqlConnectionStringBuilder 来构建字符串,但无法添加该设置。它确实出现在 .NET 4.6 中。

那么,有人知道使用 Always Encrypted 从 .NET Core 应用程序连接到 SQL Server 实例的方法吗?

【问题讨论】:

这能回答你的问题吗? SQL Server Always Encrypted with .NET Core not compatible 【参考方案1】:

从 .net Core 3.1 开始支持此功能。 我让它在生产场景中运行 请参阅我对这个 *** 问题的回答(使用 EFCore 和 Azure KeyVault):SQL Server Always Encrypted with .NET Core not compatible

【讨论】:

【参考方案2】:

只是提到here

Microsoft.data.sqlclient

【讨论】:

【参考方案3】:

现在,.NET Core 3.0 Preview 5 通过 Microsoft SQL Server 的新 Microsoft.Data.SqlClient 数据提供程序支持始终加密。

【讨论】:

是的 - 但我找不到任何示例:如何使用这个新包实现 Always Encrypted。有人知道如何使用它吗?【参考方案4】:

.Net Core 目前不支持始终加密

你可以找到支持的框架列表here

添加对 .NET 核心的支持在我们的路线图中,我们还没有时间表

请关注this github issue 获取更新

【讨论】:

你有这方面的资料吗?否则这个答案只是意见。 我应该在答案中提到这一点,但是,我是 Always Encrypted 团队的一员 :) 我也和布赖恩在一起。已经使用 .NET Core 构建了所有内容,现在唯一缺少的部分就是让 AE 工作。真的很想使用 AE 而不是合并自定义版本。此路线图有任何更新吗?也许是 .NET 核心测试的 alpha 版本? ;-) .NET Core 框架在 Always Encrypted 方面有没有进展? 状态更新将不胜感激。或者 github 中的路线图或问题的链接。【参考方案5】:

正如 MS 人员所说 - .Net Core 目前不支持 Always Encrypted。

我们遇到了同样的情况,最后我们不得不将Core WebApp 定位到.NET Full Framework,没有其他选择。

即当前不兼容 netcoreapp1.1 (.NETCoreApp,Version=v1.1) 兼容 net452 (.NETFramework,Version=v4.5.2)

来自论坛 - “尽管 EF Core 可以用于 AE 功能,但底层 .NET Core SQL 客户端目前仍不支持 AE,它将成为他们(MS + 开源贡献者)未来的里程碑支持它”

请参阅GitHub page 了解有关此问题的最新更新:

【讨论】:

遇到同样的情况,所以我们在针对 .NET46 的解决方案中创建了一个新项目。认为这可行,但您的 MVC Web 应用程序还必须以 .NET46 为目标。

以上是关于在EF Core中为数据表按列加密存储的主要内容,如果未能解决你的问题,请参考以下文章

如何在 EF Core 5 中为自定义 SQL 配置导航属性

ASP MVC 应用程序中的列加密与使用 .net Core / EF Core 的 SQL Server 2016

如何在 EF Core 中通过 Fluent Api 创建加密列

EF Core 调用具有多个连接的存储过程并映射相关数据

用于在 EF Core 中与数据库通信的安全性

EF Core下利用Mysql进行数据存储在并发访问下的数据同步问题