Amo*_*kar 8 .net c# entity-framework-core asp.net-core
我目前正在设计一个需要加密存储数据的 Web 应用程序。
计划使用的技术:
这将是实现这一目标的好方法,同时仍然能够使用实体框架和 LINQ,因此开发人员不必处理加密。
是否可以加密整个数据库?
一个好的方法是在将更改保存到数据库时加密数据,并在从数据库读取数据时解密。
我开发了一个库来在 Entity Framework Core 上下文中提供加密字段。
使用内置或自定义加密提供程序保存更改时,您可以使用我的EntityFrameworkCore.DataEncryption插件来加密字符串字段。实际上,仅AesProvider
开发了。
要使用它,只需将该属性添加[Encrypted]
到模型的字符串属性中,然后重写类OnModelCreating()
中的方法DbContext
,然后modelBuilder.UseEncryption(...)
通过向其传递加密提供程序(AesProvider
或从 继承的任何类IEncryptionProvider
)来调用 。
public class UserEntity
{
public int Id { get; set; }
[Encrypted]
public string Username { get; set; }
[Encrypted]
public string Password { get; set; }
public int Age { get; set; }
}
public class DatabaseContext : DbContext
{
// Get key and IV from a Base64String or any other ways.
// You can generate a key and IV using "AesProvider.GenerateKey()"
private readonly byte[] _encryptionKey = ...;
private readonly byte[] _encryptionIV = ...;
private readonly IEncryptionProvider _provider;
public DbSet<UserEntity> Users { get; set; }
public DatabaseContext(DbContextOptions options)
: base(options)
{
this._provider = new AesProvider(this._encryptionKey, this._encryptionIV);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseEncryption(this._provider);
}
}
Run Code Online (Sandbox Code Playgroud)
保存结果:
希望能帮助到你。
小智 7
首先,不要将encrypting 与 hashing混淆,在 Eastrall 的回答中,它们暗示您可以对密码字段使用加密。不要这样做
此外,每次加密新值时都应该更改初始化向量,这意味着您应该避免像 Eastrall 的库那样为整个数据库设置单个 IV 的实现。
现代加密算法的设计速度很慢,因此加密数据库中的所有内容至少会略微影响您的性能。
如果处理得当,您的加密负载将不仅仅是密文,还应该包含加密密钥的 ID、所用算法的详细信息和签名。这意味着与等效的纯文本相比,您的数据将占用更多空间。看看https://github.com/blowdart/AspNetCoreIdentityEncryption如果你想看到你如何能实现一个自己。(该项目中的自述文件无论如何都值得一读)
考虑到这一点,您项目的最佳解决方案可能取决于将这些成本最小化对您来说有多重要。
如果您要Aes.Create();
像 Eastrall 的答案中的库一样使用 .NET Core ,则密文将是一种byte[]
类型。您可以将数据库提供程序中的列类型用于byte[]
,或者您可以编码为 base64 并存储为string
. 通常存储为字符串是值得的:base64 将比 多占用 33% 的空间byte[]
,但更容易使用。
我建议使用ASP.NET Core 数据保护堆栈而不是Aes
直接使用类,因为它可以帮助您进行密钥轮换并为您处理 base64 中的编码。您可以将它安装到您的 DI 容器中services.AddDataProtection()
,然后让您的服务依赖于IDataProtectionProvider
,可以像这样使用:
// 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);
Run Code Online (Sandbox Code Playgroud)
当然,阅读文档,不要只是复制上面的代码。
在ASP.NET Core Identity 中,IdentityUserContext
使用值转换器来加密标记有该[ProtectedPersonalData]
属性的个人数据。Eastrall 的图书馆也在使用ValueConverter
.
这种方法很方便,因为它不需要您在实体中编写代码来处理转换,如果您遵循域驱动设计方法(例如.NET Architecture Seedwork),这可能不是一种选择。
但是有一个缺点。如果您的实体上有很多受保护的字段。下面的代码会导致user
对象上的每个加密字段都被解密,即使没有一个被读取。
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);
user.EmailVerified = true;
await context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)
您可以通过在您的属性上使用 getter 和 setter 来避免使用值转换器,如下面的代码。但是,这意味着您需要在实体中放置特定于加密的代码,并且您必须连接到任何加密提供程序的访问权限。这可能是一个static
类,或者你必须以某种方式传递它。
private string secret;
public string Secret {
get => SomeAccessibleEncryptionObject.Decrypt(secret);
set => secret = SomeAccessibleEncryptionObject.Encrypt(value);
}
Run Code Online (Sandbox Code Playgroud)
然后,您每次访问该属性时都会进行解密,这可能会在其他地方导致您意想不到的麻烦。例如,如果下面的代码emailsToCompare
非常大,则可能会非常昂贵。
foreach (var email in emailsToCompare) {
if(email == user.Email) {
// do something...
}
}
Run Code Online (Sandbox Code Playgroud)
您可以看到您需要在实体本身或提供程序中记住您的加密和解密调用。
避免使用值转换器同时仍然对实体外部或数据库配置隐藏加密是复杂的。因此,如果性能是一个很大的问题,以至于您无法使用值转换器,那么您的加密可能不是您可以从应用程序的其余部分隐藏的东西,您可能希望运行Protect()
和Unprotect()
调用在完全在您的实体框架代码之外的代码中。
这是一个示例实现,其灵感来自 ASP.NET Core Identity 中的值转换器设置,但使用的是IDataProtectionProvider
代替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)
{
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,加密的责任很复杂,我建议您确保您牢牢掌握要进行的任何设置,以防止数据丢失等情况丢失您的加密密钥。此外,来自 OWASP Cheatsheet Series的DotNet Security CheatSheet是一个有用的阅读资源。
归档时间: |
|
查看次数: |
7558 次 |
最近记录: |