首先,不要混淆encrypting with hashing,在 Eastrall 的回答中,他们暗示您可以对密码字段使用加密。 不要这样做
此外,您应该在每次加密新值时更改初始化向量,这意味着您应该避免像 Eastrall 的库那样为整个数据库设置单个 IV 的实现。
现代加密算法的设计速度很慢,因此对数据库中的所有内容进行加密至少会略微影响您的性能。
如果处理得当,您的加密负载将不仅仅是密文,还应包含加密密钥的 ID、所用算法的详细信息和签名。这意味着与纯文本等价物相比,您的数据将占用更多空间。如果您想了解如何自己实现它,请查看https://github.com/blowdart/AspNetCoreIdentityEncryption。 (该项目中的自述文件无论如何都值得一读)
考虑到这一点,您项目的最佳解决方案可能取决于将这些成本降至最低对您来说有多重要。
如果您要使用 Eastrall 答案中的库中的 .NET Core Aes.Create();,则密文将是 byte[] 类型。您可以将数据库提供程序中的列类型用于byte[],或者您可以编码为base64 并存储为string。通常存储为字符串是值得的:base64 将比byte[] 多占用大约 33% 的空间,但更易于使用。
我建议使用ASP.NET Core Data Protection stack 而不是直接使用Aes 类,因为它可以帮助您进行密钥轮换并为您处理base64 编码。您可以使用 services.AddDataProtection() 将其安装到您的 DI 容器中,然后让您的服务依赖于 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);
当然,read the documentation,不要只是复制上面的代码。
在ASP.NET Core Identity, the IdentityUserContext 中,使用值转换器对标有[ProtectedPersonalData] 属性的个人数据进行加密。
Eastrall 的图书馆也在使用ValueConverter。
这种方法很方便,因为它不需要您在实体中编写代码来处理转换,如果您遵循领域驱动设计方法(例如.NET Architecture Seedwork),这可能不是一个选项。
但是有一个缺点。如果您的实体上有很多受保护的字段。下面的代码将导致user 对象上的每个加密字段都被解密,即使没有一个被读取。
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);
user.EmailVerified = true;
await context.SaveChangesAsync();
您可以避免使用值转换器,而是在您的属性上使用 getter 和 setter,如下面的代码。然而,这意味着您将需要在您的实体中放置特定于加密的代码,并且您必须连接对您的加密提供者的访问。这可能是一个 static 类,或者您必须以某种方式将其传递。
private string secret;
public string Secret {
get => SomeAccessibleEncryptionObject.Decrypt(secret);
set => secret = SomeAccessibleEncryptionObject.Encrypt(value);
}
您每次访问该属性时都会解密,这可能会在其他地方给您带来意想不到的麻烦。例如,如果emailsToCompare 非常大,下面的代码可能会非常昂贵。
foreach (var email in emailsToCompare) {
if(email == user.Email) {
// do something...
}
}
您可以看到,您需要在实体本身或提供程序中记住您的加密和解密调用。
避免使用值转换器,同时仍然对实体外部隐藏加密或数据库配置很复杂。因此,如果性能问题如此严重以至于您无法使用值转换器,那么您的加密可能无法隐藏在应用程序的其余部分之外,您可能希望运行 @987654347 @ 和 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)
{
}
}
}
最后,加密的责任很复杂,我建议您确保牢牢掌握所采用的任何设置,以防止数据丢失等事情丢失您的加密密钥。此外,OWASP 备忘单系列中的DotNet Security CheatSheet 是一个有用的阅读资源。