Armazene dados confidenciais com a criptografia em nível de campo do lado do cliente do Python & MongoDB
Avalie esse Início rápido
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.
- 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.
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:
1 python -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
.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 3 import os 4 from pathlib import Path 5 from secrets import token_bytes 6 7 from bson import json_util 8 from bson.binary import STANDARD 9 from bson.codec_options import CodecOptions 10 from pymongo import MongoClient 11 from pymongo.encryption import ClientEncryption 12 from 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: 4 key_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 script
create_key.py
:1 # create_key.py 2 3 # Configure a single, local KMS provider, with the saved key: 4 kms_providers = {"local": {"key": key_bytes}} 5 csfle_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: 10 with 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
__keystore
no 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 3 Path("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
__keystore
e 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 3 schema = { 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 17 json_schema = json_util.dumps( 18 schema, json_options=json_util.CANONICAL_JSON_OPTIONS, indent=2 19 ) 20 Path("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__keystore
contendo 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
.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 3 import os 4 from pathlib import Path 5 6 from pymongo import MongoClient 7 from pymongo.encryption_options import AutoEncryptionOpts 8 from pymongo.errors import EncryptionError 9 from bson import json_util 10 11 # Load the master key from 'key_bytes.bin': 12 key_bin = Path("key_bytes.bin").read_bytes() 13 14 # Load the 'person' schema from "json_schema.json": 15 collection_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: 4 kms_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: 9 csfle_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 people
no 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.json
e 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:1 key_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: 5 with 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 collection
people
. Em seguida, ele adiciona um novo documento de pessoa, paraSophyia Duleep Singir, com um valorssn
fictí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*: 5 with 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: 5 try: 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"})) 12 except 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: 2 csfle_opts_no_schema = AutoEncryptionOpts( 3 kms_providers, 4 "csfle_demo.__keystore", 5 ) 6 with 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. 19 with 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 script
create_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 3 print("Creating 'people' collection in 'csfle_demo' database (with schema) ...") 4 client.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.py
e 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 3 csfle_opts = AutoEncryptionOpts( 4 kms_providers, 5 "csfle_demo.__keystore", 6 )
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.
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.