在本指南中,您可以了解如何使用 EF Core 提供商通过 Queryable Encryption (QE) 加密特定文档字段。
Overview
Queryable Encryption 在将数据写入 MongoDB 之前,在应用程序层对敏感文档字段进行加密,同时仍允许应用程序查询这些字段。只有有权访问加密密钥的应用程序才能读取明文数据。如果攻击者获取对数据库的访问权,他们只能看到密文,因为他们无法访问加密密钥。
例如,您可以使用 Queryable Encryption 加密包含以下内容的字段:
社会安全号
信用卡号码
健康或医疗信息
财务信息
任何其他敏感信息或个人可识别信息
EF Core 提供商通过流畅模型 API 支持 Queryable Encryption。您可以使用 OnModelCreating() 方法配置要加密的实体属性,提供商在读取和写入数据时会自动处理加密。
先决条件
在使用 EF Core 提供商配置 Queryable Encryption 之前,请确保具备以下条件:
运行 MongoDB 7.0 或更高版本的 MongoDB Enterprise 或 MongoDB Atlas 集群。
访问 Key Management Service (KMS)。支持的 KMS 提供商包括 Amazon Web Services、Azure、Google Cloud Platform、密钥管理互操作协议 (KMIP)和本地密钥提供商。
环境中安装的自动加密共享库 (
CRYPT_SHARED) 或mongocryptd。要了解如何安装共享库,请参阅 MongoDB Server 文档中的 安装查询分析组件。
启用可查询加密
以下部分介绍如何在上下文中配置加密选项,以及如何在模型中标记实体属性以进行加密:
配置加密选项
在配置使用 Queryable Encryption 的任何上下文之前,必须通过运行以下代码为应用程序注册一次自动加密提供商:
MongoClientSettings.Extensions.AddAutoEncryption();
然后,创建 MongoOptionsExtension 实例,并在将实例传递给 UseMongoDB() 方法之前链接以下方法:
WithKmsProviders()— 指定您的 KMS 提供商和凭证。WithKeyVaultNamespace()— 指定用于存储数据加密密钥的集合。WithCryptProvider()— 指定要使用的加密库及其路径。
以下示例配置本地 KMS 提供商以供开发使用:
var kmsProviders = new Dictionary< string, IReadOnlyDictionary<string, object>> { { "local", new Dictionary<string, object> { { "key", localMasterKey } } } }; var keyVaultNamespace = CollectionNamespace.FromFullName( "encryption.__keyVault"); var mongoOptions = new MongoOptionsExtension() .WithConnectionString(connectionString) .WithDatabaseName("myDatabase") .WithKmsProviders(kmsProviders) .WithKeyVaultNamespace(keyVaultNamespace) .WithCryptProvider( CryptProvider.AutoEncryptSharedLibrary, Environment.GetEnvironmentVariable("CRYPT_SHARED_LIB_PATH")); var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>() .UseMongoDB(mongoOptions);
要使用 mongocryptd 而不是共享库,请将 CryptProvider.Mongocryptd 和 mongocryptd 二进制文件的路径传递给 WithCryptProvider() 方法。
警告
不要在生产环境中使用本地 KMS 提供商。如果没有远程 KMS,则存在未经授权访问主密钥或永久丢失解密数据所需密钥的风险。
标记加密字段
在 OnModelCreating() 方法中,对要加密的每个属性调用加密方法。您选择的方法控制该字段上可用的查询类型:
方法 | 查询支持 | 注意 |
|---|---|---|
| 无 | 在没有查询支持的情况下加密字段。用于存储但从不直接过滤的字段。 |
| 相等性 ( | 不适用于 |
| 范围 ( | 仅支持 |
要加密所有的实体而不是标量属性,请在 OwnsOne() 或 OwnsMany() 返回的 OwnedNavigationBuilder 或 OwnershipBuilder 上调用 IsEncrypted(dataKeyId)。
有关使用上述方法的完整示例,请参阅“示例:加密患者数据”部分。
示例:加密患者数据
以下 Patient 实体定义 document model,其中 SSN 和 DateOfBirth 字段用于加密:
public class Patient { public ObjectId Id { get; set; } public string Name { get; set; } = null!; public string SSN { get; set; } = null!; public DateTime DateOfBirth { get; set; } }
以下 HospitalContext 标记了 OnModelCreating() 方法中用于加密的字段。SSN 在没有查询支持的情况下加密,DateOfBirth 在具有范围查询支持的情况下加密。构造函数接受两个数据加密密钥的 ID,您在初始化上下文之前在密钥保管库中创建这些 ID:
public class HospitalContext : DbContext { public DbSet<Patient> Patients { get; set; } = null!; private readonly Guid _ssnDataKeyId; private readonly Guid _dobDataKeyId; public HospitalContext( DbContextOptions options, Guid ssnDataKeyId, Guid dobDataKeyId) : base(options) { _ssnDataKeyId = ssnDataKeyId; _dobDataKeyId = dobDataKeyId; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Patient>(entity => { entity.ToCollection("patients"); entity.Property(p => p.SSN) .IsEncrypted(_ssnDataKeyId); entity.Property(p => p.DateOfBirth) .IsEncryptedForRange( new DateTime(1900, 1, 1), new DateTime(2100, 12, 31), _dobDataKeyId); }); } }
以下代码配置加密选项,使用数据密钥 ID 实例化 HospitalContext,并插入和查询 Patient 文档:
var mongoOptions = new MongoOptionsExtension() .WithConnectionString("<connection string URI>") .WithDatabaseName("hospitalDb") .WithKmsProviders(kmsProviders) .WithKeyVaultNamespace(keyVaultNamespace) .WithCryptProvider( CryptProvider.AutoEncryptSharedLibrary, Environment.GetEnvironmentVariable("CRYPT_SHARED_LIB_PATH")); using var context = new HospitalContext( new DbContextOptionsBuilder<HospitalContext>() .UseMongoDB(mongoOptions) .Options, ssnDataKeyId, dobDataKeyId); context.Database.EnsureCreated(); context.Patients.Add(new Patient { Name = "John Doe", SSN = "123-45-6789", DateOfBirth = new DateTime(1985, 6, 15) }); context.SaveChanges(); var results = context.Patients .Where(p => p.DateOfBirth > new DateTime(1980, 1, 1)) .ToList();
服务器端模式支持
默认情况下,EF Core 提供商仅将字段加密配置应用于客户端。单客户端加密适用于开发过程,因为加密模式可能仍在演进。
对于生产部署,在创建集合时在服务器上注册加密模式。服务器持有模式后,它会独立于客户端配置实施加密,从而防止因配置错误的客户端导致意外的明文写入。在服务器上注册模式后,您无法更改加密字段,除非重新创建集合。
要创建具有服务器端模式的集合,请将上下文的 Model 传递给 QueryableEncryptionSchemaGenerator.GenerateSchemas()。然后,将结果传递给 CreateCollection() 方法,如以下示例所示:
var encryptedSchemas = QueryableEncryptionSchemaGenerator.GenerateSchemas( context.Model); using var client = new MongoClient( "<connection string URI>"); var database = client.GetDatabase("hospitalDb"); foreach (var entityType in context.Model .GetEntityTypes() .Where(e => e.IsDocumentRoot())) { var collectionName = entityType.GetCollectionName(); if (encryptedSchemas.TryGetValue( collectionName, out var schema)) { database.CreateCollection( collectionName, new CreateCollectionOptions { EncryptedFields = schema }); } } context.Database.EnsureCreated();
在服务器上存在加密集合后,您可以配置新的上下文实例以仅使用服务器模式。在 MongoOptionsExtension 上设置 QueryableEncryptionSchemaMode.Ignore,如下例所示:
var mongoOptions = new MongoOptionsExtension() .WithConnectionString("<connection string URI>") .WithDatabaseName("hospitalDb") .WithKmsProviders(kmsProviders) .WithKeyVaultNamespace(keyVaultNamespace) .WithCryptProvider( CryptProvider.AutoEncryptSharedLibrary, Environment.GetEnvironmentVariable("CRYPT_SHARED_LIB_PATH")) .WithQueryableEncryptionSchemaMode( QueryableEncryptionSchemaMode.Ignore);
在 Ignore 模式下,OnModelCreating() 方法中的任何 IsEncrypted 配置对加密都没有影响。服务器模式控制哪些字段可以加密。
更多信息
要了解有关 Queryable Encryption 的更多信息,请参阅 MongoDB Server 手册中的 Queryable Encryption 和 Queryable Encryption 使用案例 章节。
For complete 实现 details, see the EF Core 提供商 API 文档。