使用ASP.NET核心实体框架的数据层中的数据加密 [英] Data Encryption in Data Layer with ASP.NET Core Entity Framework

查看:74
本文介绍了使用ASP.NET核心实体框架的数据层中的数据加密的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在设计一个Web应用程序,其中的数据需要加密存储。

I am currently designing a web application where the data needs to be stored encrypted.

使用的计划技术:

ASP.NET Core API
ASP.NET Core实体框架
MS SQL Server 2012
任何Web前端
由于规范,我们需要将所有加密的数据存储在

ASP.NET Core API ASP.NET Core Entity Framework MS SQL Server 2012 any Web Frontend Because of the specification, we need to store all data encrypted in the database.

哪一种是实现此目标的好方法,同时仍然可以使用Entity Framework& LINQ,因此开发人员不必照顾加密。

Which would be a good approach to achieve this while still be able to use the Entity Framework & LINQ, so the developer does not have to take care of the encryption.

是否可以加密整个数据库?

Is it possible to encrypt the whole database?

推荐答案

首先,不要混淆使用散列加密,在Eastrall的回答中,它们暗示您可以对密码字段使用加密。 请勿这样做

First of all, don't confuse encrypting with hashing, in Eastrall's answer they imply that you could use encryption for a password field. Do not do this

此外,每次加密新值时都应更改初始化向量,这意味着您应避免使用Eastrall的实现

Also, you should change the initialisation vector every time you encrypt a new value, which means you should avoid implementations like Eastrall's library that set a single IV for the whole database.

现代加密算法的设计速度很慢,因此对数据库中的所有内容进行加密将至少对性能产生一定的影响。

Modern encryption algorithms are designed to be slow, so encrypting everything in your database is going to affect your performance at least marginally.

如果操作正确,加密的有效负载将不仅是密文,还应包含加密密钥的ID,使用的算法的详细信息,和签名。这意味着与纯文本相比,您的数据将占用更多空间。如果想了解如何查看 https://github.com/blowdart/AspNetCoreIdentityEncryption 自己实现。 (该项目中的自述文件还是值得一读的)。

If done properly, your encrypted payload is not going to just be the cipher text, but should also contain the ID of the encryption key, details about the algorithm used, and a signature. This means your data is going to take up a lot more space compared to the plain text equivalent. Take a look at https://github.com/blowdart/AspNetCoreIdentityEncryption if you want to see how you could implement that yourself. (The readme in that project is worth reading anyway)

记住这一点,针对您的项目的最佳解决方案可能取决于对您而言,将这些成本降至最低的重要性。

With that in mind, the best solution for your project might depend on how important it is for you to minimise those costs.

如果您要使用.NET Core Aes.Create(); 在Eastrall的答案中,密文将为 byte [] 类型。您可以将数据库提供程序中的列类型用于 byte [] ,也可以编码为base64并存储为 string 。通常,将其存储为字符串是值得的:base64将比 byte [] 占用大约33%的空间,但更易于使用。

If you're going to use the .NET Core Aes.Create(); like in the library in Eastrall's answer, the cipher text is going to be a byte[] type. You could use the column type in your database provider for byte[], or you could encode as base64 and store as a string. Typically storing as a string is worthwhile: base64 will take up about 33% more space than byte[], but is easier to work with.

我建议使用 ASP.NET Core数据保护堆栈,而不是直接使用 Aes 类,因为它有助于您进行键旋转并处理其中的编码base64为您。您可以使用 services.AddDataProtection()将其安装到DI容器中,然后使服务依赖于 IDataProtectionProvider ,后者可以这样使用:

I suggest making use of the ASP.NET Core Data Protection stack instead of using the Aes classes directly, as it helps you do key rotation and handles the encoding in base64 for you. You can install it into your DI container with services.AddDataProtection() and then have your services depend upon IDataProtectionProvider, which can be used like this:

// Make sure you read the docs for ASP.NET Core Data Protection!

// protect
var payload = dataProtectionProvider
    .CreateProtector("<your purpose string here>")
    .Protect(plainText);

// unprotect
var plainText = dataProtectionProvider
    .CreateProtector("<your purpose string here>")
    .Unprotect(payload);

当然, 阅读文档 ,而不仅仅是复制上面的代码。

Of course, read the documentation and don't just copy the code above.

ASP.NET核心身份, IdentityUserContext 使用值转换器来加密个人数据标记有 [ProtectedPersonalData] 属性。
Eastrall的库也使用了 ValueConverter

这种方法很方便,因为它不需要您要在实体中编写代码来处理转换,如果您遵循域驱动设计方法(例如,如。NET体系结构Seedwork )。

This approach is handy because it doesn't require you to write code in your entities to handle conversion, something that might not be an option if you are following a Domain Driven Design approach (e.g. like the .NET Architecture Seedwork).

但是有一个缺点。如果您的实体上有很多受保护的字段。下面的代码将导致 user 对象上的每个加密字段都被解密,即使没有读取单个字段。

But there is a drawback. If you have a lot of protected fields on your entity. The code below would cause every single encrypted field on the user object to get decrypted, even though not a single one is being read.

var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);
user.EmailVerified = true;
await context.SaveChangesAsync();

您可以避免使用值转换器,而是在属性上使用getter和setter,如下面的代码。但是,这意味着您将需要在实体中放置特定于加密的代码,并且您必须连接对任何加密提供程序的访问权限。这可能是静态类,或者您必须以某种方式传递它。

You could avoid using a value converter by instead using a getter and setter on your property like the code below. However that means you will need to place encryption specific code in your entity, and you will have to wire up access to whatever your encryption provider is. This could be a static class, or you'll have to pass it in somehow.

private string secret;

public string Secret {
  get => SomeAccessibleEncryptionObject.Decrypt(secret);
  set => secret = SomeAccessibleEncryptionObject.Encrypt(value);
}

然后您每次访问该属性时都会解密,这可能会导致您其他地方的意外麻烦。例如,如果 emailsToCompare 很大,下面的代码可能会非常昂贵。

You would then be decrypting every time you access the property, which could cause you unexpected trouble elsewhere. For example the code below could be very costly if emailsToCompare was very large.

foreach (var email in emailsToCompare) {
  if(email == user.Email) {
    // do something...
  }
}

您可以看到,您需要记住加密和解密的呼叫,可以在实体本身或提供者中。

You can see that you'd need to memoize your encrypt and decrypt calls, either in the entity itself or in the provider.

避免值转换器,同时仍然从实体外部隐藏加密,否则数据库配置很复杂。因此,如果性能非常重要,而值转换器则无法解决,那么加密可能就不会被其他应用程序所掩盖,而您希望运行 Protect() Unprotect()完全在您的实体框架代码之外调用代码。

Avoiding the value converter while still hiding the encryption from outside the entity or the database configuration is complex. And so if performance is so much of an issue that you can't go with the value converters, then your encryption is possibly not something that you can hide away from the rest of your application, and you would want to be running the Protect() and Unprotect() calls in code completely outside of your Entity Framework code.

这里是一个示例实现,灵感来自ASP.NET Core Identity中的值转换器设置,但使用了 IDataProtectionProvider 而不是 IPersonalDataProtector

Here is an example implementation inspired by the value converter setup in ASP.NET Core Identity but using an IDataProtectionProvider instead of IPersonalDataProtector:

public class ApplicationUser
{
    // other fields...

    [Protected]
    public string Email { get; set; }
}

public class ProtectedAttribute : Attribute
{
}

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions options)
        : base(options)
    {
    }

    public DbSet<ApplicationUser> Users { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        // other setup here..
        builder.Entity<ApplicationUser>(b =>
        {
            this.AddProtecedDataConverters(b);
        });
    }

    private void AddProtecedDataConverters<TEntity>(EntityTypeBuilder<TEntity> b)
        where TEntity : class
    {
        var protectedProps = typeof(TEntity).GetProperties()
            .Where(prop => Attribute.IsDefined(prop, typeof(ProtectedAttribute)));

        foreach (var p in protectedProps)
        {
            if (p.PropertyType != typeof(string))
            {
                // You could throw a NotSupportedException here if you only care about strings
                var converterType = typeof(ProtectedDataConverter<>)
                    .MakeGenericType(p.PropertyType);
                var converter = (ValueConverter)Activator
                    .CreateInstance(converterType, this.GetService<IDataProtectionProvider>());

                b.Property(p.PropertyType, p.Name).HasConversion(converter);
            }
            else
            {
                ProtectedDataConverter converter = new ProtectedDataConverter(
                    this.GetService<IDataProtectionProvider>());

                b.Property(typeof(string), p.Name).HasConversion(converter);
            }
        }
    }

    private class ProtectedDataConverter : ValueConverter<string, string>
    {
        public ProtectedDataConverter(IDataProtectionProvider protectionProvider)
            : base(
                    s => protectionProvider
                        .CreateProtector("personal_data")
                        .Protect(s),
                    s => protectionProvider
                        .CreateProtector("personal_data")
                        .Unprotect(s),
                    default)
        {
        }
    }

    // You could get rid of this one if you only care about encrypting strings
    private class ProtectedDataConverter<T> : ValueConverter<T, string>
    {
        public ProtectedDataConverter(IDataProtectionProvider protectionProvider)
            : base(
                    s => protectionProvider
                        .CreateProtector("personal_data")
                        .Protect(JsonSerializer.Serialize(s, default)),
                    s => JsonSerializer.Deserialize<T>(
                        protectionProvider.CreateProtector("personal_data")
                        .Unprotect(s),
                        default),
                    default)
        {
        }
    }
}

最后,加密的责任很复杂,我建议您确保对所用的任何设置都有牢牢的掌握,以防止诸如数据丢失之类的丢失加密密钥的事情。另外,OWASP备忘单系列中的 DotNet Security CheatSheet 是一种有用的阅读资源。

Finally, the responsibility of encryption is complex and I would recommend ensuring you have a firm grasp of whatever setup you go with to prevent things like data loss from losing your encryption keys. Also, the DotNet Security CheatSheet from the OWASP Cheatsheet Series is a useful resource to read.

这篇关于使用ASP.NET核心实体框架的数据层中的数据加密的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆