字段加密和可查询性
Overview
了解以下 Queryable Encryption 主题:
对加密字段启用查询时的注意事项。
如何指定加密字段。
如何配置加密字段,使其可供查询。
查询类型以及哪些类型可用于加密字段。
如何优化加密字段的查询性能。
启用查询时的注意事项
使用“可查询加密”时,可以选择是否将加密字段设置为可查询。如果不需要执行需要查询加密字段的 CRUD 操作,则可能不需要启用对该字段的查询。您仍然可以通过查询其他可查询或未加密的字段来检索整个文档。
当您将加密字段设置为可查询时,Queryable Encryption 会为每个加密字段创建一个索引,这会使该字段的写入操作花费更长时间。 当写入操作更新索引字段时,MongoDB 也会更新相关索引。
当您创建加密集合时,MongoDB 会创建两个元数据集合,因此需要更大的存储空间。
指定加密字段
使用“可查询加密”,你就可以使用 JSON 加密模式指定要在 MongoDB 文档中自动加密的字段。加密模式定义了加密哪些字段,以及这些字段可以进行哪些查询。
重要
您可以指定除 _id
字段之外的任何字段进行加密。
若要指定用于加密和查询的字段,请创建包含以下属性的加密模式:
密钥名称 | 类型 | 必需 |
---|---|---|
path | 字符串 | 必需 |
bsonType | 字符串 | 必需 |
keyId | 二进制文件 | 必需。 为每个字段指定键值。 注意如果调用 |
queries | 对象 | 可选。包含此项可以让字段成为可查询字段。 |
例子
该示例说明了如何创建加密模式。
考虑以下包含个人身份信息 (PII)、信用卡信息和敏感医疗信息的文档:
{ "firstName": "Jon", "lastName": "Snow", "patientId": 12345187, "address": "123 Cherry Ave", "medications": [ "Adderall", "Lipitor" ], "patientInfo": { "ssn": "921-12-1234", "billing": { "type": "visa", "number": "1234-1234-1234-1234" } } }
为确保 PII 和敏感医疗信息的安全,请创建加密模式并配置这些字段以进行自动加密。 您必须提前为每个加密字段生成唯一密钥。 示例:
const encryptedFieldsObject = { fields: [ { path: "patientId", keyId: "<unique data encryption key>", bsonType: "int" }, { path: "patientInfo.ssn", keyId: "<unique data encryption key>", bsonType: "string" }, { path: "medications", keyId: "<unique data encryption key>", bsonType: "array" }, { path: "patientInfo.billing", keyId: "<unique data encryption key>", bsonType: "object" } ] }
在客户端上配置AutoEncryptionSettings
,然后使用createEncryptedCollection()
辅助方法创建您的集合。
配置查询字段
在字段中包含 queries
属性以使它们可查询。这使授权客户端能够对这些字段发出读取和写入查询。忽略 queries
属性会阻止客户端查询字段。
例子
在上一个示例模式中添加 queries
属性,将 patientId
和 patientInfo.ssn
字段设置为可查询。
const encryptedFieldsObject = { fields: [ { path: "patientId", bsonType: "int", queries: { queryType: "equality" } }, { path: "patientInfo.ssn", bsonType: "string", queries: { queryType: "equality" } }, { path: "medications", bsonType: "array" }, { path: "patientInfo.billing", bsonType: "object" }, ] }
争用
并发写操作,例如在多个文档中连续插入相同的字段/值对,可能会导致争用:即延迟操作的冲突。
通过Queryable Encryption, MongoDB使用内部计数器跟踪加密集合中每个字段/值对的出现次数。 争用因子对该计数器进行分区,类似于大量。 这样可以最大限度地减少使用insert
、 update
或findAndModify
连续添加或修改具有相同字段/值对的加密字段时递增计数器的问题。 contention = 0
创建一个大量,在索引0处包含一个元素。 contention = 4
会在索引0 - 4处创建一个包含5元素的大量。 MongoDB在插入期间递增随机大量元素。
未设置时, contention
默认为8
,可为大多数工作负载提供高性能。 较高的争用可提高低关联基数字段上的插入和更新操作的性能,但会降低查找性能。
调整争用因子
您可以选择在可查询字段中包含 contention
属性,以变更争用因子的默认值 8
。在修改争用因子之前,请考虑以下几点:
仅当字段存在频繁的并发写入操作时,才考虑将 contention
增加到默认值 8
以上。由于较高的争用值会牺牲查找性能以支持插入和更新操作,因此对于很少更新的字段,较高的争用系数不太可能利大于弊。
如果经常查询某个字段,但很少写入,请考虑减少contention
。 在这种情况下,查找性能优于写入和更新性能。
您可以使用公式计算字段的争用因子,其中
ω
是短时间内(例如30毫秒)对该字段执行的并发写入操作的数量。 如果未知,可以使用服务器的虚拟核心数。valinserts
是自上次执行元数据压缩以来插入的唯一字段/值对的数量。ω
是{ω/valinserts
四舍五入到最接近的整数。 对于具有1000近期值100/1000 = 0.1
的100操作的工作负载,四舍五入为1
。
合理的争用因子cf
是以下公式的结果,四舍五入到最接近的正整数:
(ω
∗ · (ω
∗ − 1)) / 0.2
例如,如果在 30 毫秒内对某个字段执行 100 个并发写入操作,则为 ω = 100
。如果该字段有 50 个最近唯一值,则为 ω
∗ = 100/50 = 2
。其结果为 cf = (2·1)/0.2 = 10
。
警告
不要在数据本身的属性上设立争用因子,例如字段/值对的频率(关联基数)。 仅根据工作负载设立争用因子。
考虑ω = 100
和valinserts = 1000
的情况,产生ω
* =
100/1000 = 0.1 ≈ 1
和cf = (1·0)/0.2 = 0 ≈ 1
。 其中的值的20出现频率很高,因此您改为设立contention = 3
。 可以访问权限多个数据库快照的攻击者可以推断出较高的设置表示存在频繁的字段/值对。 在这种情况下,保持contention
未设置,使其默认为8
可以防止攻击者获取该信息。
有关争用及其加密影响的完整信息,请参阅 MongoDB 的 Queryable Encryption技术论文中的“Section 9 :指南”
查询类型
将查询类型传递给加密字段对象中的queries
选项可设置该字段允许的查询类型。使用受支持的查询类型查询非加密字段或加密字段会返回加密数据,然后在客户端对这些数据进行解密。
Queryable Encryption 目前支持 none
和 equality
查询类型。如果未指定查询类型,则默认为 none
。如果查询类型为 none
,则字段已加密,但客户端无法查询。
equality
查询类型支持以下表达式:
注意
即使使用受支持的查询运算符,如果查询将加密字段与 null
或正则表达式进行比较,也会导致错误。
当操作将加密字段与以下任何 BSON 类型比较时,“可查询加密”equality
查询不支持对字段的读取或写入操作:
double
decimal128
object
array
客户端和服务器模式
MongoDB 支持使用模式验证来实施集合中特定字段的加密。使用自动 Queryable Encryption 的客户端具有特定行为,具体取决于数据库连接配置:
如果连接
encryptedFieldsMap
对象包含指定集合的密钥,则客户端使用该对象执行自动 Queryable Encryption,而不是使用远程模式。至少,本地规则必须加密远程模式标记为需要加密的那些字段。如果连接
encryptedFieldsMap
对象不包含指定集合的密钥,客户端会下载该集合的服务器端远程模式,然后用于执行自动“可查询加密”。重要
行为注意事项
如果客户端不存在适用于指定集合的加密模式,则会出现以下情况:
客户端相信服务器具有适用于自动 Queryable Encryption 的有效模式。
客户端仅使用远程模式来执行自动 Queryable Encryption。客户端不会实施该模式中指定的任何其他验证规则。
要了解有关自动 Queryable Encryption 的详情,请参阅以下资源:
启用可查询加密
在创建集合之前启用可查询加密。 在创建集合后启用 Queryable Encryption 不会加密该集合中已有文档的字段。 您可以通过以下两种方式之一对字段启用 Queryable Encryption:
将
encryptedFieldsObject
常量表示的加密模式传递给应用程序用于创建集合的客户端:
const client = new MongoClient(uri, { autoEncryption: { keyVaultNameSpace: "<your keyvault namespace>", kmsProviders: "<your kms provider>", extraOptions: { cryptSharedLibPath: "<path to Automatic Encryption Shared Library>" }, encryptedFieldsMap: { "<databaseName.collectionName>": { encryptedFieldsObject } } } ... await client.db("<database name>").createCollection("<collection name>"); }
有关 autoEncryption
配置选项的更多信息,请参阅 MongoClient 的 Queryable Encryption 选项一节。
将加密的字段对象传递给
createCollection()
以创建新集合:
await encryptedDB.createCollection("<collection name>", { encryptedFields: encryptedFieldsObject });
提示
在创建集合时,以及在创建客户端以访问集合时,可以指定加密字段。此操作可确保如果服务器的安全性遭到破坏,信息仍通过客户端加密。
重要
显式创建集合,而不是使用插入操作隐式创建集合。在您使用 createCollection()
创建集合时,MongoDB 在加密字段上创建索引。如果没有该索引,对加密字段的查询可能运行缓慢。