Implementando o direito ao apagamento com o CSFLE
Tom McCarthy, Pierre Petersson7 min read • Published Feb 22, 2023 • Updated Mar 08, 2023
Avalie esse Artigo
O direito ao apagamento, também conhecido como direito a ser esquematizado, é um direito concedido a pessoas singulares protegidas pelo GDPR. Isso significa que as empresas que armazenam os dados pessoais de um indivíduo devem poder excluí-los quando solicitado. Como esses dados podem estar espalhados por vários sistemas, pode ser técnico um desafio para essas empresas identificá-los e removê-los de todos os lugares. Mesmo que isso seja executado corretamente, também há o risco de que os dados excluídos possam ser restaurados a partir de backups no futuro, potencialmente contribuindo para riscos legais e financeiros.
Esta postagem do blog aborda esses desafios, demonstrando como você pode usar a criptografia de nível de campo do lado do cliente do MongoDB para fortalecer os procedimentos de remoção de dados confidenciais.
Aviso: não oferecemos garantias de que a solução e as técnicas descritas neste artigo atendam aos requisitos regulatórios sobre o direito de exclusão. Cada organização precisa fazer sua própria determinação sobre medidas apropriadas ou suficientes para cumprir com vários requisitos regulatórios, como o GDPR.
A destruição criptográfica é uma técnica de destruição de dados que consiste em destruir as chaves de criptografia que permitem que os dados sejam descriptografados, tornando-os indecifráveis. O exemplo abaixo fornece uma explicação mais detalhada.
Imagine que você esteja armazenando dados para vários usuários. Você começa dando a cada usuário sua própria chave de encriptação de dados (DEK) exclusiva e mapeando-a para esse cliente. Isso é representado no diagrama abaixo, onde "Usuário A" e "Usuário B" têm sua própria chave no armazenamento de chaves. Essa DEK pode ser usada para criptografar e descriptografar quaisquer dados relacionados ao usuário em questão.
Vamos supor que queremos remover todos os dados do usuário B. Se removermos o DEK do usuário B, não poderemos mais descriptografar nenhum dado que foi criptografado com ele; tudo o que resta em nosso armazenamento de dados é o texto cifrado " junk ". Como ilustra o diagrama abaixo, os dados do usuário A não são afetados, mas não podemos mais ler os dados do usuário B.
Com a criptografia de nível de campo do lado do clientedo MongoDB (CSFLE), os aplicativos podem criptografar campos confidenciais em documentos antes de transmitir dados para o servidor. Isso significa que, mesmo quando os dados estão sendo usados pelo banco de dados na memória, eles nunca estão em texto sem formatação. O banco de dados só vê os dados criptografados, mas ainda permite que você os consulte.
O MongoDB CSFLE utiliza criptografia de envelope, que é a prática de criptografar dados de texto simples com uma chave de dados, que por sua vez é criptografada por uma chave de envelope de nível superior (também conhecida como "chave mestre").
As chaves de envelope geralmente são gerenciadas por um Serviço de gerenciamento de chaves (KMS). O MongoDB CSFLE é compatível com vários KMSs, como AWS KMS, GCP KMS, Azure KeyVault e Keystores que oferecem suporte ao padrão KMIP (por exemplo, Hashicorpo Keyvault).
CSFLE pode ser usado no modoautomático , no modo explícito ou em uma combinação de ambos. O modo Automático permite que você execute operações de leitura e gravação criptografadas com base em um esquema de criptografia definido, evitando a necessidade de o código do aplicativo especificar como criptografar ou descriptografar campos. Esse esquema de criptografia é um JSON document que define quais campos precisam ser criptografados. O modoexplícito refere-se ao uso da biblioteca de criptografia do MongoDB para criptografar ou descriptografar manualmente campos em seu aplicativo.
Neste artigo, vamos usar a técnica decriptografia explícita para mostrar como podemos usar técnicas de fragmentação de criptografia com o CSFLE para implementar (ou aumentar) procedimentos para "esquecer" dados confidenciais. Usaremos o AWS KMS para demonstrar isso.
Com o MongoDB como nosso banco de dados, podemos usar o CSFLE para implementar a fragmentação de criptografia, para que possamos fornecer garantias mais fortes em torno da privacidade dos dados.
Para demonstrar como você pode implementar isso, vamos orientá-lo em um aplicativo de demonstração. O aplicativo de demonstração é um aplicativo Web python (Flask) com um front-end, que expõe a funcionalidade de inscrição, logon e um formulário de entrada de dados. Também adicionamos uma página de "admin" para mostrar a funcionalidade relacionada à destruição de criptografia. Se você quiser acompanhar, poderá executar o aplicativo por conta própria — você encontrará o código e as instruções necessárias no GitHub.
Quando um usuário se inscreve, nosso aplicativo gera uma DEK para o usuário e, em seguida, armazena o ID da DEK junto com outros detalhes do usuário. A geração de chaves é feita por meio do método
create_data_key
na classeClientEncryption
, que inicializamos anteriormente como app.mongodb_encryption_client
. Esse cliente de criptografia é responsável por gerar uma DEK, que neste caso será criptografada pela chave de envelope. Em nosso caso, o cliente de criptografia é configurado para usar uma chave de envelope do AWS KMS.1 # flaskapp/db_queries.py 2 3 4 def create_key(userId): 5 data_key_id = \ 6 app.mongodb_encryption_client.create_data_key(kms_provider, 7 master_key, key_alt_names=[userId]) 8 return data_key_id
Podemos então usar esse método ao salvar o usuário.
1 # flaskapp/user.py 2 3 def save(self): 4 dek_id = db_queries.create_key(self.username) 5 result = app.mongodb[db_name].user.insert_one( 6 { 7 "username": self.username, 8 "password_hash": self.password_hash, 9 "dek_id": dek_id, 10 "createdAt": datetime.now(), 11 } 12 ) 13 if result: 14 self.id = result.inserted_id 15 return True 16 else: 17 return False
Uma vez inscrito, o usuário pode fazer login, após o qual pode inserir dados por meio de um formulário mostrado na captura de tela abaixo. Esses dados têm um "nome" e um "valor", permitindo que o usuário armazene pares de valores-chave arbitrários.
No banco de dados, armazenaremos esses dados em uma MongoDB collection chamada "data, " em documentos estruturados assim:
1 { 2 "name": "shoe size", 3 "value": "10", 4 "username": "tom" 5 }
Para esta demonstração, optamos por criptografar os campos de valor e nome de usuário deste documento. Esses campos serão criptografados usando a DEK criada na inscrição pertencente ao usuário conectado.
1 # flaskapp/db_queries.py 2 3 # Fields to encrypt, and the algorithm to encrypt them with 4 ENCRYPTED_FIELDS = { 5 # Deterministic encryption for username, because we need to search on it 6 "username": Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic, 7 # Random encryption for value, as we don't need to search on it 8 "value": Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, 9 }
Em seguida, a função insert_data percorre os campos que queremos criptografar e o algoritmo que estamos usando para cada um deles.
1 # flaskapp/db_queries.py 2 3 def insert_data(document): 4 document["username"] = current_user.username 5 # Loop over the field names (and associated algorithm) we want to encrypt 6 for field, algo in ENCRYPTED_FIELDS.items(): 7 # if the field exists in the document, encrypt it 8 if document.get(field): 9 document[field] = encrypt_field(document[field], algo) 10 # Insert document (now with encrypted fields) to the data collection 11 app.data_collection.insert_one(document)
Se os campos especificados existirem no documento, isso chamará nossa função encrypt_field para executar a criptografia usando o algoritmo especificado.
1 # flaskapp/db_queries.py 2 3 # Encrypt a single field with the given algorithm 4 5 def encrypt_field(field, algorithm): 6 try: 7 field = app.mongodb_encryption_client.encrypt( 8 field, 9 algorithm, 10 key_alt_name=current_user.username, 11 ) 12 return field 13 except pymongo.errors.EncryptionError as ex: 14 # Catch this error in case the DEK doesn't exist. Log a warning and 15 # re-raise the exception 16 if "not all keys requested were satisfied" in ex._message: 17 app.logger.warn( 18 f"Encryption failed: could not find data encryption key for user: {current_user.username}" 19 ) 20 raise ex
Depois que os dados forem adicionados, eles serão mostrados no aplicativo da web:
Agora vejamos o que acontece se excluirmos a DEK. Para fazer isso, podemos ir para a página de administração. Essa página de administração só deve ser fornecida a pessoas que precisem gerenciar chaves, e temos algumas opções:
Vamos usar a opção "Excluir chave de criptografia de dados", que removerá a DEK, mas deixará todos os dados inseridos pelo usuário intactos. Depois disso, o aplicativo não poderá mais recuperar os dados que foram armazenados por meio do formulário. Ao tentar recuperar os dados do usuário conectado, será gerado um erro
Observação: depois de executarmos a exclusão da chave de dados, o aplicativo da Web ainda poderá descriptografar e exibir os dados por um curto período de tempo antes que o cache expire - isso leva no máximo 60 segundos.
Mas o que realmente resta no banco de dados? Para ter uma visualização disso, você pode Go à página do administrador e escolher "Obter dados para todos os usuários". Nesta visualização, não lançaremos uma exceção se não pudermos descriptografar os dados. Vamos apenas mostrar exatamente o que armazenamos no banco de dados. Embora não tenhamos realmente excluído os dados do usuário, porque a chave de criptografia de dados não existe mais, tudo o que podemos ver agora é o texto cifrado para os campos criptografados "nome de usuário" e "valor".
E aqui está o código que estamos usando para buscar os dados nesta visualização. Como você pode ver, usamos uma lógica muito semelhante ao método encrypt mostrado anteriormente. Executamos uma operação de busca sem nenhum filtro para recuperar todos os dados de nossa coleção de dados. Em seguida, percorreremos nosso dicionário ENCRYPTED_FIELDS para ver quais campos precisam ser descriptografados.
1 # flaskapp/db_queries.py 2 3 def fetch_all_data_unencrypted(decrypt=False): 4 results = list(app.data_collection.find()) 5 6 if decrypt: 7 for field in ENCRYPTED_FIELDS.keys(): 8 for result in results: 9 if result.get(field): 10 result[field], result["encryption_succeeded"] = decrypt_field(result[field]) 11 return results
A função decrypt_field é chamada para cada campo a ser descriptografado, mas, nesse caso, detectaremos o erro se não pudermos descriptografá-lo com êxito devido a uma DEK ausente.
1 # flaskapp/db_queries.py 2 3 # Try to decrypt a field, returning a tuple of (value, status). This will be either (decrypted_value, True), or (raw_cipher_text, False) if we couldn't decrypt 4 def decrypt_field(field): 5 try: 6 # We don't need to pass the DEK or algorithm to decrypt a field 7 field = app.mongodb_encryption_client.decrypt(field) 8 return field, True 9 # Catch this error in case the DEK doesn't exist. 10 except pymongo.errors.EncryptionError as ex: 11 if "not all keys requested were satisfied" in ex._message: 12 app.logger.warn( 13 "Decryption failed: could not find data encryption key to decrypt the record." 14 ) 15 # If we can't decrypt due to missing DEK, return the "raw" value. 16 return field, False 17 raise ex
Também podemos usar o shell
mongosh
para verificar diretamente no banco de dados, apenas para provar que não há nada lá que possamos ler.Neste ponto, leitores experientes podem estar se fazendo a pergunta: "Mas e se restaurarmos o banco de dados a partir de um backup?" Se quisermos evitar isso, podemos usar dois clusters de banco de dados separados em nosso aplicativo - um para armazenar dados e outro para armazenar DEKs (o cofre de chaves "" ). Essa teoria é aplicada no aplicativo de amostra, que exige que você especifique duas cadeias de conexão do MongoDB — uma para dados e outra para o cofre de chaves. Se usarmos clusters separados, ele desacoplará a restauração de backups para dados de aplicativos e o cofre de chaves; A restauração de um backup no cluster de dados não restaurará nenhuma DEKs que tenha sido excluída do cluster do Key Vault.
Nesta publicação do blog, demonstramos como a criptografia no nível do campo do MongoDB pode ser usada para simplificar a tarefa de "esquecer" determinados dados. Com uma única operação " delete data key ", podemos efetivamente esquecer os dados que podem estar armazenados em diferentes bancos de dados, collection, backups e registros. Em um aplicativo de produção real, podemos excluir todos os dados do usuário que pudermos encontrar, além de remover sua DEK. Essa abordagem "defense in depth" nos ajuda a garantir que os dados realmente desapareceram. Ao implementar a fragmentação criptográfica, o impacto é muito menor se uma operação de exclusão falhar ou perder alguns dados que deveriam ter sido apagados.
Você pode encontrar mais detalhes sobre a criptografia no nível do campo do lado do cliente do MongoDB em nossa documentação. Se você tiver dúvidas, fique à vontade para fazer uma postagem nos fóruns de nossa comunidade.