Docs 菜单
Docs 主页
/ / /
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 及更高版本,则应按照 手册中“可查询加密的Queryable Encryption 加密共享库”页面上的说明安装自动加密共享库MongoDB Server 。

所需步骤如下:

  1. 导航至MongoDB 下载中心

  2. 从版本下拉列表中,选择x.y.z (current) (当前最新版本)。

  3. 在平台下拉列表中,选择您的平台。

  4. 在 Package(包)下拉列表中,选择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 Server 的企业版( 4.2及更高版本)中。 有关安装说明,请参阅MongoDB 手册

MongoDB Ruby 驱动程序使用 ffi gemlibmongocrypt 调用函数。由于此 gem 不是驱动程序的依赖项,因此需要手动将其添加到Gemfile中:

gem 'ffi'

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

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

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

警告

使用本地主密钥不安全。 建议使用远程 KMS 来创建和存储主密钥。 为此,请按照 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

注意

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

  • MongoDBCSFLE 有一些限制, MongoDB Server手册中的 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 遭到泄露,则可以解密所有加密数据。

重要

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

要详细了解为何应使用远程 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 轮换数据密钥。

以下是使用Amazon Web Services 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 对类型敏感,例如,您不能在声明为String的字段中存储整数。

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

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

后退

常见错误