文档菜单
文档首页
/ / /
Mongoid
/

自动客户端字段级加密

本页内容

  • 安装
  • 安装libmongocrypt
  • 安装自动加密共享库(Ruby驱动程序2.19+)
  • 安装 mongocryptd(Ruby驱动程序2.18或更早版本)
  • ffi 添加到您的 Gemfile
  • 创建客户主密钥
  • 配置客户端
  • 创建数据加密密钥
  • 配置加密模式
  • 已知限制
  • 数据处理
  • 加密密钥管理
  • 客户主密钥
  • 数据加密密钥
  • 加密密钥轮换
  • 将自动加密添加到现有项目

自4.2版本起,MongoDB支持客户端字段级加密(CSFLE)。这是一个功能,允许您在将数据通过网络发送到MongoDB之前对应用中的数据进行加密。启用CSFLE后,没有任何MongoDB产品可以访问未加密的数据。

您可以使用以下机制设置CSFLE:

  • 自动加密:允许您执行加密的读取和写入操作,而无需编写代码来指定如何加密字段。

  • 显式加密:允许您通过MongoDB驱动程序的加密库执行加密的读取和写入操作。您必须使用此库在您的应用程序中指定加密逻辑。

从版本9.0开始,Mongoid支持CSFLE的自动加密功能。本教程将指导您在Mongoid中设置和使用CSFLE的过程。

注意

本教程不涵盖所有CSLFE功能。您可以在服务器文档中找到有关MongoDB CSFLE的更多信息。

注意

如果您想使用显式FLE,请参考Ruby驱动程序文档

您可以在驱动程序文档中找到如何安装必需依赖项的详细说明。

请注意您应用程序中使用的Ruby驱动程序的版本,并选择下面的适当步骤。

这可以通过以下两种方式之一完成。

如果您使用的是 Ruby 驱动程序版本 2.19 及以上,应按照服务器手册中 自动加密共享库 页面上的说明进行安装。

所需步骤如下

  1. 导航到 MongoDB 下载中心

  2. 从版本下拉菜单中选择 x.y.z (当前)(最新的当前版本)。

  3. 在平台下拉菜单中选择您的平台。

  4. 在包下拉菜单中选择 crypt_shared

  5. 点击下载。

解压后,确保在您的 mongoid.yml 中配置了库的完整路径,如下所示

development:
clients:
default:
options:
auto_encryption_options:
extra_options:
crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so'

如果您使用的是较旧的Ruby驱动程序版本,则需要手动安装 mongocryptdmongocryptd 随企业版MongoDB服务器(版本4.2及更高版本)预包装。有关安装说明,请参阅MongoDB手册

MongoDB Ruby驱动程序使用ffi宝石libmongocrypt 调用函数。由于此宝石不是驱动程序的依赖项,因此需要手动添加到您的 Gemfile

gem 'ffi'

客户主密钥(CMK)用于加密数据加密密钥。最简单的方法是使用本地存储的96字节密钥。您可以使用以下Ruby代码生成此类密钥

require 'securerandom'
SecureRandom.hex(48) # => "f54ab...."

在本教程的后续部分,我们假设客户主密钥可以从CUSTOMER_MASTER_KEY环境变量中获取。

警告

使用本地主密钥是不安全的。建议您使用远程密钥管理系统创建和存储主密钥。为此,请按照MongoDB客户端端加密文档中的"设置远程主密钥"步骤进行操作。

有关创建主密钥的更多信息,请参阅MongoDB手册中的创建客户主密钥部分。

自动CSFLE需要对MongoDB客户端进行一些额外配置。假设您的应用程序只有一个default客户端,您需要将以下内容添加到您的mongoid.yml

development:
clients:
default:
uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
options:
auto_encryption_options: # This key enables automatic encryption
key_vault_namespace: 'encryption.__keyVault' # Database and collection to store data keys
kms_providers: # Tells the driver where to obtain master keys
local: # We use the local key in this tutorial
key: "<%= ENV['CUSTOMER_MASTER_KEY'] %>" # Key that we generated earlier
extra_options:
crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so' # Only if you use the library

数据加密密钥(DEK)是您用于加密MongoDB文档中字段的密钥。您使用CMK加密后将其存储在密钥保管库集合中。

在Mongoid中创建数据加密密钥(DEK)可以使用db:mongoid:encryption:create_data_key Rake任务

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'KImyywsTQWi1+cFYIHdtlA==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

如果需要,您可以创建多个DEK。

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'Vxr5m+5cQISjDOruzZgE0w==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

您还可以为DEK提供备选名称。这允许您在配置字段的加密时通过名称引用DEK。它还允许您在运行时动态地将DEK分配给字段。

% rake db:mongoid:encryption:create_data_key -- --key-alt-name=my_data_key
Created data key with id: 'yjF8hKmKQsqGeFGXlB9Sow==' with key alt name: 'my_data_key' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

现在我们可以告诉Mongoid应该加密什么

class Patient
include Mongoid::Document
include Mongoid::Timestamps
# Tells Mongoid what DEK should be used to encrypt fields of the document
# and its embedded documents.
encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA=='
# This field is not encrypted.
field :category, type: String
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm.
field :passport_id, type: String, encrypt: {
deterministic: false
}
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
# algorithm.
field :blood_type, type: String, encrypt: {
deterministic: true
}
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm and using a different data key.
field :ssn, type: Integer, encrypt: {
deterministic: false, key_id: 'Vxr5m+5cQISjDOruzZgE0w=='
}
embeds_one :insurance
end
class Insurance
include Mongoid::Document
include Mongoid::Timestamps
field :insurer, type: String
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm using the key which alternate name is stored in the
# policy_number_key field.
field :policy_number, type: Integer, encrypt: {
deterministic: false,
key_name_field: :policy_number_key
}
embedded_in :patient
end

注意

如果您正在开发Rails应用程序,建议在mongoid.yml中将preload_models设置为true。这将确保在应用程序启动之前,Mongoid加载所有模型,并在读取或写入任何数据之前配置加密模式。

  • MongoDB CSFLE有一些限制,这些限制在服务器手册的CSFLE限制页面中描述。这些限制也适用于Mongoid。

  • Mongoid不支持加密embeds_many关系。

  • 如果您使用:key_name_field选项,该字段必须使用非确定性算法进行加密。为了以确定性方式加密字段,您必须指定:key_id选项。

在许多情况下,自动使用CSFLE是透明的。

注意

以下代码示例中,我们假设存在一个名为 unencrypted_client 的变量,它是一个连接到同一数据库但没有加密的 MongoClient。我们使用此客户端来演示实际上在数据库中持久化的是什么。

文档可以像往常一样创建,字段将根据配置进行加密和解密

Patient.create!(
category: 'ER',
passport_id: '123456',
blood_type: 'AB+',
ssn: 98765,
insurance: Insurance.new(insurer: 'TK', policy_number: 123456, policy_number_key: 'my_data_key')
)
# Fields are encrypted in the database
unencrypted_client['patients'].find.first
# =>
# {"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4292'),
# "category"=>"ER",
# "passport_id"=><BSON::Binary:0x404080 type=ciphertext data=0x012889b2cb0b1341...>,
# "blood_type"=><BSON::Binary:0x404560 type=ciphertext data=0x022889b2cb0b1341...>,
# "ssn"=><BSON::Binary:0x405040 type=ciphertext data=0x012889b2cb0b1341...>,
# "insurance"=>{"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4293'), "insurer"=>"TK", "policy_number"=><BSON::Binary:0x405920 type=ciphertext data=0x012889b2cb0b1341...>}, "policy_number_key"=>"my_data_key"}

使用确定性算法加密的字段可以进行查询。仅支持精确匹配查询。有关更多详细信息,请参阅服务器文档

# We can find documents by deterministically encrypted fields.
Patient.where(blood_type: "AB+").to_a
# => [#<Patient _id: 6447e34d46ebfd3debdd9c39, category: "ER", passport_id: "123456", blood_type: "AB+", ssn: 98765>]

您的客户主密钥是您用于加密数据加密密钥的密钥。MongoDB在创建数据加密密钥时自动使用指定的CMK来加密数据加密密钥。

CMK是CSFLE中最敏感的密钥。如果您的CMK受到损害,所有加密数据都可以被解密。

重要

请确保您在远程KMS上存储您的客户主密钥(CMK)。

想了解更多关于为什么应该使用远程KMS的原因,请参阅使用远程KMS的原因。

要查看所有支持的KMS提供程序列表,请参阅KMS提供程序页面。

MongoDB CSFLE支持以下密钥管理系统(KMS)提供程序

可以使用db:mongoid:encryption:create_data_key Rake任务创建数据加密密钥。默认情况下,它们存储在与数据库相同的集群上。但是,将密钥分别存储可能是个好主意。这可以通过在mongoid.yml中指定密钥库客户端来实现

development:
clients:
key_vault:
uri: mongodb+srv://user:pass@anothercluster.mongodb.net/blog_development?retryWrites=true&w=majority
default:
uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
options:
auto_encryption_options:
key_vault_client: :key_vault # Client to connect to key vault
# ...

可以使用Ruby驱动程序的rewrap_many_data_key方法轮换加密密钥。此方法自动解密多个数据加密密钥,并使用指定的CMK重新加密它们。然后更新密钥库集合中的轮换密钥。此方法允许您根据两个可选参数轮换加密密钥

  • 用于指定要轮换哪些密钥的过滤器。如果没有数据密钥与给定的过滤器匹配,则不会轮换任何密钥。省略过滤器以轮换密钥库集合中的所有密钥。

  • 表示新CMK的对象。省略此对象以使用当前CMK轮换数据密钥。

以下是一个使用AWS KMS轮换密钥的示例

# Create a key vault client
key_vault_client = Mongo::Client.new('mongodb+srv://user:pass@yourcluster.mongodb.net')
# Or, if you declared the key value client in mongoid.yml, use it
key_vault_client = Mongoid.client(:key_vault)
# Create the encryption object
encryption = Mongo::ClientEncryption.new(
key_vault_client,
key_vault_namespace: 'encryption.__keyVault',
kms_providers: {
aws: {
"accessKeyId": "<IAM User Access Key ID>",
"secretAccessKey": "<IAM User Secret Access Key>"
}
}
)
encryption.rewrap_many_data_key(
{}, # We want to rewrap all keys
{
provider: 'aws',
master_key: {
region: 'us-east-2',
key: 'arn:aws:kms:us-east-2:...'
}
}
)

MongoDB自动CSFLE支持原地加密。您可以为现有数据库启用加密,同时仍然可以读取未加密的数据。所有写入数据库的数据都将被加密。然而,一旦启用加密,所有查询操作都将使用加密数据。

# We assume that there are two documents in the database, one created without
# encryption enabled, and one with encryption.
# We can still read both.
Patient.all.to_a
# =>
# [#<Patient _id: 644937ac46ebfd02468e58c8, category: "ER", passport_id: "DE-1257", blood_type: "AB+", ssn: 123456>,
# #<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]
# But when we query, we can see only the latter one.
Patient.where(blood_type: 'AB+').to_a
# => [#<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]

如果想要加密现有数据库,可以通过读取和回写所有数据来实现,即使没有任何更改。如果决定这样做,请记住以下几点。

  • 验证现有数据的完整性以保证数据一致性。CSFLE是类型敏感的——例如,您不能在声明为的字段中存储整数。

  • 对于字符串,请确保空值始终为空字符串或未设置,而不是nil(CSFLE不支持原生的null)。

  • 此操作需要应用程序停机。

返回

常见错误