在本指南中,您可以了解如何使用 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)和本地密钥提供商。
The Automatic Encryption Shared Library (
CRYPT_SHARED) ormongocryptdinstalled in your environment. To learn how to install the shared library, see Install a Query Analysis Component in the MongoDB Server documentation.
启用可查询加密
以下部分介绍如何在上下文中配置加密选项,以及如何在模型中标记实体属性以进行加密:
配置加密选项
在配置使用 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)。
For a complete example that uses the preceding methods, see the Example: Encrypt Patient Data section.
示例:加密患者数据
以下 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 配置对加密都没有影响。服务器模式控制哪些字段可以加密。
更多信息
To learn more about Queryable Encryption, see the Queryable Encryption and Queryable Encryption Use Cases sections in the MongoDB Server manual.
For complete 实现 details, see the EF Core 提供商 API 文档。