Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

Armazene dados confidenciais com a criptografia em nível de campo do lado do cliente do Python & MongoDB

Mark Smith11 min read • Published Feb 05, 2022 • Updated Sep 23, 2022
MongoDBSegurançaPython
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty
Logotipo do QuickStart Python
Com uma combinação de legações sobre proteção de dados de clientes (como o GDPR) e o aumento da legação de questões sobre luva devalores, é cada vez mais necessário armazenar dados confidenciais de clientes com segurança. Embora a segurança padrão do MongoDB seja baseada em padrões modernos do setor, como TLS para a camada de transporte e SCRAM-SHA-2356 para troca de senhas, ainda é possível que alguém entre em seu banco de dados vetor ou obtendo de alguma forma suas credenciais de segurança.
Nessas situações, você pode adicionar uma camada extra de segurança aos campos mais confidenciais do seu banco de dados usando aCSFLE ( criptografia no nível do campo). O CSFLE criptografa determinados campos que você especifica, dentro do driver, no cliente, para que nunca seja transmitido sem criptografia, nem visto sem criptografia pelo servidor MongoDB. O CSFLE torna quase impossível obter informações confidenciais do servidor do banco de dados diretamente por meio da interceptação de dados do cliente ou da leitura de dados diretamente do disco, mesmo com credenciais de DBA ou root.
Há duas maneiras de usar o CSFLE no MongoDB: Explicit, onde seu código precisa criptografar manualmente os dados antes de serem enviados ao driver para ser inserido ou atualizado usando métodos auxiliares; e implícito, onde você declara em sua coleção quais campos devem ser criptografados usando um JSON schema estendido, e isso é feito pelo driver Python sem nenhuma alteração de código. Este tutorial abordará o CSFLE implícito , que está disponível somente em MongoDB Enterprise e MongoDB Atlas. Se você estiver executando o MongoDB Community, precisará usar CSFLE explícito, que não será abordado aqui.

Pré-requisitos

  • Uma versão recente do Python 3. O código nesta publicação foi escrito para 3.8, mas qualquer versão do Python 3.6+ deve estar bem.
  • Um MongoDB Atlas cluster executando MongoDB 4.2 ou posterior.

Configuração

Há duas coisas que você precisa ter instalado no seu servidor de aplicativos para habilitar o CSFLE no driver PyMongo. A primeira é uma biblioteca Python chamada pymongocrypt, que você pode instalar executando o seguinte com seu virtualenv habilitado:
1python -m pip install "pymongo[encryption,srv]~=3.11"
O [encryption] entre colchetes informa ao pip para instalar as dependências opcionais necessárias para criptografar dados no driver PyMongo.
A segunda coisa que você precisa ter instalado é o mongocryptd, que é um aplicativo fornecido como parte do MongoDB Enterprise. Siga as instruções para instalar o mongocryptd na máquina que você usará para executar seu código Python. Em um ambiente de produção, é recomendável executar o mongocryptd como um serviço na inicialização de sua VM ou container.
Teste se você tem o mongocryptd instalado em seu caminho executando mongocryptd, garantindo que ele imprima alguma saída. Você pode então desligá-lo novamente com Ctrl-C.

Criando uma chave para criptografar e descriptografar seus dados

Primeiro, mostrarei como escrever um script para gerar uma nova chave mestra secreta que será usada para proteger chaves de campo individuais. Neste tutorial, usaremos uma chave mestra "local" que será armazenada no lado do aplicativo em linha no código ou em um arquivo de chave local. Observe que um arquivo de chave local deve ser usado somente em desenvolvimento. Para produção, é altamente recomendável usar um dos serviços integrados de gerenciamento de chaves na nuvem nativa ou recuperar a chave mestra de um gerenciador de segredos, como o Hashicorp Vault. Este script Python gerará alguns bytes aleatórios para serem usados como uma chave mestra secreta. Em seguida, ele criará uma nova chave de campo no MongoDB, criptografada usando a chave mestra. A chave mestra será gravada em um arquivo para que possa ser carregada por outros scripts Python, junto com um documento do JSON schema que dirá ao PyMongo quais campos devem ser criptografados e como.
Todo o código descrito nesta publicação está no GitHub. Recomendamos que você confira se tiver dúvidas, caso contrário, vale a pena seguir o tutorial e escrever o código você mesmo!
Primeiro, aqui estão algumas importações de que você precisará. Cole-as em um arquivo chamado create_key.py.
1# create_key.py
2
3import os
4from pathlib import Path
5from secrets import token_bytes
6
7from bson import json_util
8from bson.binary import STANDARD
9from bson.codec_options import CodecOptions
10from pymongo import MongoClient
11from pymongo.encryption import ClientEncryption
12from pymongo.encryption_options import AutoEncryptionOpts
A primeira coisa que você precisa fazer é gerar 96 bytes de dados aleatórios. Felizmente, o Python vem com um módulo para exatamente este propósito, chamado secrets. Você pode usar o métodotoken_bytes para isso:
1# create_key.py
2
3# Generate a secure 96-byte secret key:
4key_bytes = token_bytes(96)
Em seguida, aqui está algum código que cria um MongoClient, configurado com um sistema de gerenciamento de chaves (KMS) local.
Observação: armazenar a chave mestra, não criptografada, em um sistema de arquivos local (que é o que faço neste código de demonstração) é inseguro. Em produção, você deve usar um KMS seguro, como AWS KMS, Azure Key Vaultou Cloud KMS do Google.
Abordarei isso em uma postagem do blog posterior, mas se quiser começar agora, leia a documentação
Adicione este código ao seu scriptcreate_key.py:
1# create_key.py
2
3# Configure a single, local KMS provider, with the saved key:
4kms_providers = {"local": {"key": key_bytes}}
5csfle_opts = AutoEncryptionOpts(
6 kms_providers=kms_providers, key_vault_namespace="csfle_demo.__keystore"
7)
8
9# Connect to MongoDB with the key information generated above:
10with MongoClient(os.environ["MDB_URL"], auto_encryption_opts=csfle_opts) as client:
11 print("Resetting demo database & keystore ...")
12 client.drop_database("csfle_demo")
13
14 # Create a ClientEncryption object to create the data key below:
15 client_encryption = ClientEncryption(
16 kms_providers,
17 "csfle_demo.__keystore",
18 client,
19 CodecOptions(uuid_representation=STANDARD),
20 )
21
22 print("Creating key in MongoDB ...")
23 key_id = client_encryption.create_data_key("local", key_alt_names=["example"])
Depois que o cliente é configurado no código acima, ele é usado para descartar qualquer banco de dados " csfle_demo " existente, apenas para garantir que a execução desse ou de outros scripts não faça com que seu banco de dados fique em um estado estranho.
A configuração e o cliente são então usados para criar um objeto ClientEncryption que você usará uma vez para criar uma chave de dados na collection __keystoreno banco de dadoscsfle_demo. create_data_key criará um documento na coleção __keystore que se parecerá um pouco com isto:
1{
2 '_id': UUID('00c63aa2-059d-4548-9e18-54452195acd0'),
3 'creationDate': datetime.datetime(2020, 11, 24, 11, 25, 0, 974000),
4 'keyAltNames': ['example'],
5 'keyMaterial': b'W\xd2"\xd7\xd4d\x02e/\x8f|\x8f\xa2\xb6\xb1\xc0Q\xa0\x1b\xab ...'
6 'masterKey': {'provider': 'local'},
7 'status': 0,
8 'updateDate': datetime.datetime(2020, 11, 24, 11, 25, 0, 974000)
9}
Agora você tem duas chaves! Um são os 96 bytes aleatórios que você gerou com token_bytes - essa é a chave mestre (que permanece fora do banco de dados). E há outra chave na collection__keystore! Isso ocorre porque o MongoDB CSFLE usa criptografia de envelope. A chave que é realmente usada para criptografar valores de campo é armazenada no banco de dados, mas é armazenada criptografada com a chave mestre que você gerou.
Para ter certeza de não perder a chave mestre, aqui está algum código que você deve adicionar ao seu script para salvá-lo em um arquivo chamado key_bytes.bin.
1# create_key.py
2
3Path("key_bytes.bin").write_bytes(key_bytes)
Por fim, você precisa de uma estrutura de JSON schema que informe ao PyMongo quais campos precisam ser criptografados e como. O esquema precisa fazer referência à chave que você criou em __keystoree você tem isso na variávelkey_id, então este script é um bom lugar para gerar o arquivo JSON. Adicione o seguinte ao final do seu script:
1# create_key.py
2
3schema = {
4 "bsonType": "object",
5 "properties": {
6 "ssn": {
7 "encrypt": {
8 "bsonType": "string",
9 # Change to "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" in order to filter by ssn value:
10 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random",
11 "keyId": [key_id], # Reference the key
12 }
13 },
14 },
15}
16
17json_schema = json_util.dumps(
18 schema, json_options=json_util.CANONICAL_JSON_OPTIONS, indent=2
19)
20Path("json_schema.json").write_text(json_schema)
Agora você pode executar este script. Primeiro, defina a variável de ambiente MDB_URL para a URL do seu Atlas cluster. O script deve criar dois arquivos localmente: key_bytes.bin, contendo sua chave mestre; e json_schema.json, contendo seu JSON schema. Em seu banco de dados, deve haver uma coleção__keystorecontendo sua nova chave de campo (criptografada)! A maneira mais fácil de verificar isso é Go para cloud.mongodb.com, encontrar seu cluster e clicar em Collections.

Execute queries usando sua chave e esquema

Crie um novo arquivo, chamado csfle_main.py. Este script se conectará ao seu cluster MongoDB usando a chave e o esquema criados executando create_key.py. Em seguida, mostrarei como inserir um documento e recuperá-lo com e sem configuração CSFLE, para mostrar como ele é armazenado criptografado e descriptografado transparentemente pelo PyMongo quando a configuração correta é fornecida.
Comece com algum código para importar os módulos necessários e carregar os arquivos salvos:
1# csfle_main.py
2
3import os
4from pathlib import Path
5
6from pymongo import MongoClient
7from pymongo.encryption_options import AutoEncryptionOpts
8from pymongo.errors import EncryptionError
9from bson import json_util
10
11# Load the master key from 'key_bytes.bin':
12key_bin = Path("key_bytes.bin").read_bytes()
13
14# Load the 'person' schema from "json_schema.json":
15collection_schema = json_util.loads(Path("json_schema.json").read_text())
Adicione a seguinte configuração necessária para conectar ao MongoDB:
1# csfle_main.py
2
3# Configure a single, local KMS provider, with the saved key:
4kms_providers = {"local": {"key": key_bin}}
5
6# Create a configuration for PyMongo, specifying the local master key,
7# the collection used for storing key data, and the json schema specifying
8# field encryption:
9csfle_opts = AutoEncryptionOpts(
10 kms_providers,
11 "csfle_demo.__keystore",
12 schema_map={"csfle_demo.people": collection_schema},
13)
O código acima é muito semelhante à configuração criada no create_key.py. Observe que, desta vez, AutoEncryptionOpts recebe um schema_map, mapeando o JSON schema carregado em relação à coleção peopleno banco de dadoscsfle_demo. Isso permitirá que o PyMongo saiba quais campos criptografar e descriptografar e quais algoritmos e chaves usar.
Neste ponto, vale a pena dar uma olhada no JSON schema que você está carregando. Ele é armazenado em json_schema.jsone deve ter a seguinte aparência:
1{
2"bsonType": "object",
3"properties": {
4 "ssn": {
5 "encrypt": {
6 "bsonType": "string",
7 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random",
8 "keyId": [
9 {
10 "$binary": {
11 "base64": "4/p3dLgeQPyuSaEf+NddHw==",
12 "subType": "04"}}]
13 }}}}
Esse esquema especifica que o campo ssn, usado para armazenar um número de previdência social, é uma string que deve ser armazenada criptografada usando o algoritmoAEAD_AES_256_CBC_HMAC_SHA_512-Random.
Se você não quiser armazenar o esquema em um arquivo ao gerar sua chave de campo no MongoDB, poderá carregar o ID da chave a qualquer momento usando os valores que definiu para keyAltNames ao criar a chave. No meu caso, defini keyAltNames como ["example"], para que eu pudesse procurá-lo usando a seguinte linha de código:
1key_id = db.__keystore.find_one({ "keyAltNames": "example" })["_id"]
Como meu código em create_key.py grava o esquema ao mesmo tempo em que gera a chave, ele já tem acesso ao ID da chave, portanto o código não precisa procurá-lo.
Adicione o seguinte código para se conectar ao MongoDB usando a configuração adicionada acima:
1# csfle_main.py
2
3# Add a new document to the "people" collection, and then read it back out
4# to demonstrate that the ssn field is automatically decrypted by PyMongo:
5with MongoClient(os.environ["MDB_URL"], auto_encryption_opts=csfle_opts) as client:
6 client.csfle_demo.people.delete_many({})
7 client.csfle_demo.people.insert_one({
8 "full_name": "Sophia Duleep Singh",
9 "ssn": "123-12-1234",
10 })
11 print("Decrypted find() results: ")
12 print(client.csfle_demo.people.find_one())
O código acima se conecta ao MongoDB e limpa todos os documentos existentes da collectionpeople. Em seguida, ele adiciona um novo documento de pessoa, paraSophyia Duleep Singir, com um valorssnfictício.
Apenas para provar que os dados podem ser lidos de volta do MongoDB e descriptografados pelo PyMongo, a última linha de código consulta o registro que acabou de ser adicionado e o imprime na tela. Quando executei este código, ele imprimiu:
1{'_id': ObjectId('5fc12f13516b61fa7a99afba'), 'full_name': 'Sophia Duleep Singh', 'ssn': '123-12-1234'}
Para provar que os dados estão criptografados no servidor, você pode se conectar ao seu cluster usando o Compass ou em cloud.mongodb.com, mas não é muito código para se conectar novamente sem configuração de criptografia e consultar o documento:
1# csfle_main.py
2
3# Connect to MongoDB, but this time without CSFLE configuration.
4# This will print the document with ssn *still encrypted*:
5with MongoClient(os.environ["MDB_URL"]) as client:
6 print("Encrypted find() results: ")
7 print(client.csfle_demo.people.find_one())
Quando executei isso, ele imprimiu:
1{
2 '_id': ObjectId('5fc12f13516b61fa7a99afba'),
3 'full_name': 'Sophia Duleep Singh',
4 'ssn': Binary(b'\x02\xe3\xfawt\xb8\x1e@\xfc\xaeI\xa1\x1f\xf8\xd7]\x1f\x02\xd8+,\x9el ...', 6)
5}
Esse é um resultado muito diferente de '123-12-1234 '! Infelizmente, quando você usa o algoritmo de criptografia aleatório, você perde a capacidade de filtrar no campo. Você pode ver isso se adicionar o seguinte código ao final do seu script e executá-lo:
1# csfle_main.py
2
3# The following demonstrates that if the ssn field is encrypted as
4# "Random" it cannot be filtered:
5try:
6 with MongoClient(os.environ["MDB_URL"], auto_encryption_opts=csfle_opts) as client:
7 # This will fail if ssn is specified as "Random".
8 # Change the algorithm to "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
9 # in client_schema_create_key.py (and run it again) for this to succeed:
10 print("Find by ssn: ")
11 print(client.csfle_demo.people.find_one({"ssn": "123-12-1234"}))
12except EncryptionError as e:
13 # This is expected if the field is "Random" but not if it's "Deterministic"
14 print(e)
Quando você executa esse bloco de código, ele imprime uma exceção dizendo: " Não é possível consultar campos criptografados com o algoritmo de criptografia aleatória... ". AEAD_AES_256_CBC_HMAC_SHA_512-Random é o algoritmo correto a ser usado para dados confidenciais que você não precisará filtrar, como condições médicas, questões de segurança etc. Ele também oferece melhor proteção contra a recuperação por análise de frequência e, portanto, provavelmente deve ser sua escolha padrão para criptografar dados confidenciais, especialmente dados de alta cardinalidade, como número de cartão de crédito, número de telefone ou... sim... um número de previdência social. Mas há uma probabilidade distinta de que você queira pesquisar alguém pelo número do Seguro Social, já que é um identificador exclusivo de uma pessoa, e você pode fazer isso criptografando-o usando o algoritmo " Deterministic ".
Para corrigir isso, abra create_key.py novamente e altere o algoritmo na definição do esquema de Random para Deterministic, para que fique assim:
1# create_key.py
2
3"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
Execute novamente create_key.py para gerar uma nova chave mestra, chave de campo e arquivo de esquema. (Essa operação também excluirá o banco de dados csfle_demo!) Executecsfle_main.py novamente. Dessa vez, o bloco de código que falhou anteriormente deverá imprimir os detalhes de Sophia Duleep Singh.
O problema com essa forma de configurar seu cliente é que, se algum outro código estiver mal configurado, ele poderá salvar valores não criptografados no banco de dados ou salvá-los usando a chave ou o algoritmo errado. Aqui está um exemplo de um código para adicionar um segundo registro para Dora Thewlis. Infelizmente, desta vez, a configuração não forneceu um schema_map! O que isso significa é que o SSN de Dora Thewlis será armazenado em texto simples.
1# Configure encryption options with the same key, but *without* a schema:
2csfle_opts_no_schema = AutoEncryptionOpts(
3 kms_providers,
4 "csfle_demo.__keystore",
5)
6with MongoClient(
7 os.environ["MDB_URL"], auto_encryption_opts=csfle_opts_no_schema
8) as client:
9 print("Inserting Dora Thewlis, without configured schema.")
10 # This will insert a document *without* encrypted ssn, because
11 # no schema is specified in the client or server:
12 client.csfle_demo.people.insert_one({
13 "full_name": "Dora Thewlis",
14 "ssn": "234-23-2345",
15 })
16
17# Connect without CSFLE configuration to show that Sophia Duleep Singh is
18# encrypted, but Dora Thewlis has her ssn saved as plaintext.
19with MongoClient(os.environ["MDB_URL"]) as client:
20 print("Encrypted find() results: ")
21 for doc in client.csfle_demo.people.find():
22 print(" *", doc)
Se você colar o código acima em seu script e executá-lo, ele deverá imprimir algo assim, demonstrando que um dos documentos tem um SSN criptografado e o outro é texto simples:
1* {'_id': ObjectId('5fc12f13516b61fa7a99afba'), 'full_name': 'Sophia Duleep Singh', 'ssn': Binary(b'\x02\xe3\xfawt\xb8\x1e@\xfc\xaeI\xa1\x1f\xf8\xd7]\x1f\x02\xd8+,\x9el\xfe\xee\xa7\xd9\x87+\xb9p\x9a\xe7\xdcjY\x98\x82]7\xf0\xa4G[]\xd2OE\xbe+\xa3\x8b\xf5\x9f\x90u6>\xf3(6\x9c\x1f\x8e\xd8\x02\xe5\xb5h\xc64i>\xbf\x06\xf6\xbb\xdb\xad\xf4\xacp\xf1\x85\xdbp\xeau\x05\xe4Z\xe9\xe9\xd0\xe9\xe1n<', 6)}
2* {'_id': ObjectId('5fc12f14516b61fa7a99afc0'), 'full_name': 'Dora Thewlis', 'ssn': '234-23-2345'}
Felizmente, o MongoDB oferece a capacidade de anexar um validador a uma collection para garantir que os dados armazenados sejam criptografados de acordo com o esquema.
Para ter um esquema definido no lado do servidor, retorne ao seu scriptcreate_key.py e, em vez de gravar o esquema em um arquivo JSON, forneça-o ao métodocreate_collection como um validador do JSON schema:
1# create_key.py
2
3print("Creating 'people' collection in 'csfle_demo' database (with schema) ...")
4client.csfle_demo.create_collection(
5 "people",
6 codec_options=CodecOptions(uuid_representation=STANDARD),
7 validator={"$jsonSchema": schema},
8)
O fornecer de um validador anexa o esquema à coleção criada, portanto, não há necessidade de salvar o arquivo localmente, não há necessidade de lê-lo no csfle_main.pye não há mais necessidade de fornecê-lo ao MongoClient. Ele será armazenado e aplicado pelo servidor. Isso simplifica o código de geração de chaves e o código para executar query no banco de dados, além de garantir que o campo SSN sempre será criptografado corretamente. Bônus!
A definição de csfle_opts passa a ser:
1# csfle_main.py
2
3csfle_opts = AutoEncryptionOpts(
4 kms_providers,
5 "csfle_demo.__keystore",
6)

Em conclusão

Ao concluir este início rápido, você aprenderá como:
  • Crie uma chave aleatória segura para criptografar chaves de dados no MongoDB.
  • Use o armazenamento de chaves local para armazenar uma chave durante o desenvolvimento.
  • Crie uma chave no MongoDB (criptografada com sua chave local) para criptografar dados no MongoDB.
  • Utilize um JSON schema para definir quais campos devem ser criptografados.
  • Atribua o JSON schema a uma collection para validar campos criptografados no servidor.
Como mencionado anteriormente, você nãodeve usar o armazenamento de chaves local para gerenciar sua chave - ele é inseguro. Você pode armazenar a chave manualmente em um KMS de sua escolha, como o HashiCorp Vault, ou, se estiver usando um dos três principais provedores de nuvem, seus serviços KMS já estão integrados ao PyMongo. Leia a documentação para saber mais.
Espero que você goste desse post! Deixe-nos saber o que você pensa nos MongoDB Community.
Há muita documentação sobre a criptografia em nível de campo do lado do cliente, em diferentes lugares. Aqui estão os Docs que considero úteis ao escrever esta publicação:
Se o CSFLE não atender aos seus requisitos de segurança, confira nossos outros Docs de segurança, que abrangem a criptografia em repouso e a configuração dacriptografia de transporte, entre outras coisas.
Como sempre, se você tiver alguma dúvida ou se criou algo legal, informe-nos nos MongoDB Community!

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Comece a usar o MongoDB Atlas sem servidor, AWS CDK e AWS sem servidor


Aug 09, 2024 | 18 min read
Início rápido

Primeiros passos no MongoDB e Starlette


Jul 12, 2024 | 5 min read
Tutorial

Desenvolver um sistema RAG com Gemma do Google, Hugging Face e MongoDB


Mar 21, 2024 | 12 min read
Artigo

8 Melhores práticas para criar aplicativos FastAPI e MongoDB


Apr 23, 2024 | 8 min read
Sumário