Como usar a criptografia no nível do campo do lado do cliente (CSFLE) do MongoDB com C#
Avalie esse exemplo de código
A criptografia no nível do campo do lado do cliente (CSFLE) fornece uma camada adicional de segurança para seus dados mais confidenciais. Usando um driverMongoDB compatível, o CSFLE criptografa determinados campos que você especifica, garantindo que eles nunca sejam transmitidos sem criptografia, nem vistos sem criptografia pelo servidor MongoDB.
Isso também significa que é quase impossível obter informações confidenciais do servidor de banco de dados. Sem acesso a uma chave específica, os dados não podem ser descriptografados e expostos, tornando os dados interceptados do cliente infrutíferos. A leitura de dados diretamente do disco, mesmo com credenciais de DBA ou root, também será impossível, pois os dados são armazenados em um estado criptografado.
Os principais aplicativos que mostram o poder da criptografia em nível de campo do lado do cliente são aqueles na área médica. Se você pensar rapidamente na última vez que visitou uma clínica, já tem um caso de uso eficaz para um aplicativo que requer uma combinação de campos criptografados e não criptografados. Quando você faz check-in em uma consulta, a pessoa pode precisar procurar por você por nome ou fornecedor de seguro. Esses são campos de dados comuns que geralmente não são criptografados. Depois, há informações mais óbvias que exigem criptografia: coisas como o número do Seguro Social, registros médicos ou o número da apólice de seguro. Para estes campos de dados, a criptografia é necessária.
Este tutorial orientará você na configuração de um sistema médico semelhante que usa criptografiaautomática em nível de campo do lado do cliente no MongoDB .NET Driver (para criptografia explícita, ou seja, manual em nível de campo no lado do cliente, confira esses Docs).
Nele, você vai:
Prepare um aplicativo de console do .NET Core
Gere chaves aleatórias seguras necessárias para o CSFLE
Configure o CSFLE no MongoClient
Veja o CSFLE em ação
💡️ Esse pode ser um tutorial intimidante, então não hesite em fazer quantas pausas precisar; na verdade, conclua as etapas em alguns dias! Eu tentei o meu melhor para garantir que cada etapa concluída atue como um ponto de salvamento natural durante todo o tutorial. :)
Vamos fazer isso passo a passo!
- Um 4 do MongoDB Atlasexecutando o MongoDB . 2 (ou posterior) OU MongoDB 4.2 Enterprise Server (ou posterior) — necessário para criptografia automática
- Driver MongoDB .NET 2.12.0-beta (ou posterior)
- Permissões do sistema de arquivos (para iniciar o processo mongocryptd, se estiver em execução localmente)
Vamos começar criando o scaffolding do nosso aplicativo de console. Abra o Visual Studio (estou usando o Visual Studio 2019 Community Edition) e crie um novo projeto. Ao selecionar um modelo, escolha a opção " Console App (.NET Core) " e siga as instruções para nomear seu projeto.
Depois que o modelo do projeto for carregado, precisaremos instalar uma de nossas dependências. No Console do Gerenciador de pacotes, use o seguinte comando para instalar o driver MongoDB:
1 Install-Package MongoDB.Driver -Version 2.12.0-beta1
💡️ Se o console do gerenciador de pacotes não estiver visível no IDE, você poderá acessá-lo em Exibir > Outro Windows > Console do gerenciador de pacotes no menu Arquivo.
A próxima dependência que você precisará instalar é mongocryptd, que é um aplicativo fornecido como parte do MongoDB Enterprise e é necessário para a criptografia automática em nível de campo. Siga as instruções para instalar o mongocryptd na sua máquina. Em um ambiente de produção, é recomendável executar o mongocryptd como um serviço na inicialização de sua VM ou container.
Agora que nosso projeto base e dependências estão definidos, podemos passar para a criação e configuração de nossas diferentes chaves de criptografia.
A criptografia em nível de campo do lado do cliente MongoDB usa uma estratégia de criptografia chamada criptografiade envelope. Esta estratégia usa dois tipos diferentes de chaves.
A primeira chave é chamada de chave de criptografia de dados, usada para criptografar/descriptografar os dados que você armazenará no MongoDB. A outra chave é chamada de chave mestra e é usada para criptografar a chave de criptografia de dados. Essa é a chave de texto simples de nível superior que sempre será necessária e é a chave que geraremos na próxima etapa.
🚨️ Antes de prosseguirmos, é importante observar que este tutorial demonstrará a geração de um arquivo de chave mestra armazenado como texto simples na raiz de nossa aplicação. Isso é adequado para fins educacionais e dedesenvolvimento , como este tutorial. Entretanto, isso NÃO deve ser feito em um ambiente deprodução !
Por quê? Nesse cenário, qualquer pessoa que obtiver uma cópia do disco ou um instantâneo de VM do servidor de aplicativos que hospeda nosso aplicativo também terá acesso a esse arquivo de chave, possibilitando o acesso aos dados do aplicativo.
Em vez disso, você deve configurar uma chave mestra em um sistema de gerenciamento de chaves, como o Azure Key Vault ou o AWS KMS, para produção.
Tenha isso em mente e assista a outra publicação que mostra como implementar o CSFLE com o Azure Key Vault!
Nesta etapa, geramos uma chave mestra de 96bytes, gerenciada localmente. Em seguida, a salvamos em um arquivo local chamado
master-key.txt
. Faremos mais algumas coisas com as chaves, portanto, crie uma classe separada chamadaKmsKeyHelper.cs
. Em seguida, adicione o seguinte código a ela:1 // KmsKeyHelper.cs 2 3 using System; 4 using System.IO; 5 6 namespace EnvoyMedSys 7 { 8 public class KmsKeyHelper 9 { 10 private readonly static string __localMasterKeyPath = "../../../master-key.txt"; 11 12 public void GenerateLocalMasterKey() 13 { 14 using (var randomNumberGenerator = System.Security.Cryptography.RandomNumberGenerator.Create()) 15 { 16 var bytes = new byte[96]; 17 randomNumberGenerator.GetBytes(bytes); 18 var localMasterKeyBase64 = Convert.ToBase64String(bytes); 19 Console.WriteLine(localMasterKeyBase64); 20 File.WriteAllText(__localMasterKeyPath, localMasterKeyBase64); 21 } 22 } 23 } 24 }
Então, o que está acontecendo aqui? Vamos dividi-lo, linha por linha:
Primeiro, declaramos e definimos uma variável privada chamada
__localMasterKeyPath
. Este contém o caminho para onde salvamos nossa chave mestra.Em seguida, criamos um método
GenerateLocalMasterKey()
. Neste método, usamos os serviços de criptografiado .NET para criar uma instância de um RandomNumberGenerator
. Usando este RandomNumberGenerator
, geramos uma chave criptograficamente forte de 96bytes. Depois de convertê-la em uma representação do Base64 , salvamos a chave no arquivomaster-key.txt
.Ótimo! Agora temos uma maneira de gerar uma chave mestra local. Vamos modificar o programa principal para usá-lo. No arquivo
Program.cs
, adicione o seguinte código:1 // Program.cs 2 3 using System; 4 using System.IO; 5 6 namespace EnvoyMedSys 7 { 8 class Program 9 { 10 public static void Main() 11 { 12 var kmsKeyHelper = new KmsKeyHelper(); 13 14 // Ensure GenerateLocalMasterKey() only runs once! 15 if (!File.Exists("../../../master-key.txt")) 16 { 17 kmsKeyHelper.GenerateLocalMasterKey(); 18 } 19 20 Console.ReadKey(); 21 } 22 } 23 }
No método
Main
, criamos uma instância de nossoKmsKeyHelper
e, em seguida, chamamos nosso métodoGenerateLocalMasterKey()
. Muito simples!Salve todos os arquivos e, em seguida, execute o programa. Se tudo der certo, você verá um console aparecer e a representação Base64 da sua chave mestra recém-gerada será impressa no console. Você também verá um novo arquivo
master-key.txt
aparecer no explorador de soluções.Agora que temos uma chave mestre, podemos criar uma chave de criptografia de dados.
A próxima chave que precisamos gerar é uma chave de criptografia de dados. Essa é a chave que o driver do MongoDB armazena em uma collection de cofre de chaves e é usada para criptografia e descriptografia automáticas.
A criptografia automática requer MongoDB Enterprise 4.2 ou um MongoDB 4.2 Atlas cluster.No entanto, adescriptografiaautomática é suportada para todos os usuários. Veja como configurar a descriptografia automática sem criptografia automática.
Vamos adicionar mais algumas linhas de código ao arquivo
Program.cs
:1 using System; 2 using System.IO; 3 using MongoDB.Driver; 4 5 namespace EnvoyMedSys 6 { 7 class Program 8 { 9 public static void Main() 10 { 11 var connectionString = Environment.GetEnvironmentVariable("MDB_URI"); 12 var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); 13 14 var kmsKeyHelper = new KmsKeyHelper( 15 connectionString: connectionString, 16 keyVaultNamespace: keyVaultNamespace); 17 18 string kmsKeyIdBase64; 19 20 // Ensure GenerateLocalMasterKey() only runs once! 21 if (!File.Exists("../../../master-key.txt")) 22 { 23 kmsKeyHelper.GenerateLocalMasterKey(); 24 } 25 26 kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithLocalKmsProvider(); 27 28 Console.ReadKey(); 29 } 30 } 31 }
Então, o que mudou? Primeiro, adicionamos uma importação adicional (
MongoDB.Driver
). Em seguida, declaramos uma variávelconnectionString
e uma keyVaultNamespace
.Para o namespace do key vault, o MongoDB criará automaticamente o banco de dados
encryption
e a coleção __keyVault
se ele não existir atualmente. Os nomes do banco de dados e da coleção eram puramente minha preferência. Você pode optar por nomeá-los com outro nome, se desejar!Em seguida, modificamos a instanciação
KmsKeyHelper
para aceitar dois parâmetros: a string de conexão e o namespace do cofre de chaves que declaramos anteriormente. Não se preocupe, em breve alteraremos nosso arquivoKmsKeyHelper.cs
para corresponder a isso.Finalmente, declaramos uma variável
kmsKeyIdBase64
e a definimos para um novo método que criaremos em breve: CreateKeyWithLocalKmsProvider();
. Isso manterá nossa chave de criptografia de dados.Em nosso código, definimos nosso URI MongoDB extraindo de variáveis de ambiente. Isso é muito mais seguro do que colar uma string de conexão diretamente em nosso código e é dimensionável em uma variedade de cenários de implementação automatizada.
Para nossos propósitos, criaremos um arquivo
launchSettings.json
.} Não confirme o arquivo
launchSettings.json
em um repositório público! Na verdade, adicione-o ao seu arquivo.gitignore
agora, se você tiver um ou planeja compartilhar este aplicativo. Caso contrário, você exporá seu URI MongoDB para o mundo!Clique com o botão direito do mouse em seu projeto e selecione " Propriedades " no menu de contexto.
As propriedades do projeto serão abertas na seção "Debug". Na área "Environment variables:", adicione uma variável chamada
MDB_URI
, seguida do URI de conexão:Que valor você define para a variável de ambiente
MDB_URI
?- MongoDB Atlas: Se estiver usando um MongoDB Atlas cluster, cole seu URI do Atlas.
- Local: se estiver executando uma instância local do MongoDB e não tiver alterado nenhuma configuração padrão, poderá usar a connection string padrão:
mongodb://localhost:27017
.
Após adicionar o
MDB_URI
, salve as propriedades do projeto. Você verá que um arquivolaunchSettings.json
será gerado automaticamente para você! Agora, quaisquer chamadasEnvironment.GetEnvironmentVariable()
serão extraídas deste arquivo.Com estas alterações, agora temos que modificar e adicionar mais alguns métodos à classe
KmsKeyHelper
. Vamos fazer isso agora.Primeiro, adicione essas importações adicionais:
1 // KmsKeyHelper.cs 2 3 using System.Collections.Generic; 4 using System.Threading; 5 using MongoDB.Bson; 6 using MongoDB.Driver; 7 using MongoDB.Driver.Encryption;
Em seguida, declare duas variáveis privadas e crie um construtor que aceite uma connection string e um namespace do cofre de chaves. Precisaremos dessas informações para criar nossa chave de criptografia de dados; isso também facilita a extensão e a integração com um KMS remoto posteriormente.
1 // KmsKeyhelper.cs 2 3 private readonly string _mdbConnectionString; 4 private readonly CollectionNamespace _keyVaultNamespace; 5 6 public KmsKeyHelper( 7 string connectionString, 8 CollectionNamespace keyVaultNamespace) 9 { 10 _mdbConnectionString = connectionString; 11 _keyVaultNamespace = keyVaultNamespace; 12 }
Após o método GenerateLocalMasterKey(), adicione os seguintes novos métodos. Não se preocupar, analisaremos cada um deles:
1 // KmsKeyHelper.cs 2 3 public string CreateKeyWithLocalKmsProvider() 4 { 5 // Read Master Key from file & convert 6 string localMasterKeyBase64 = File.ReadAllText(__localMasterKeyPath); 7 var localMasterKeyBytes = Convert.FromBase64String(localMasterKeyBase64); 8 9 // Set KMS Provider Settings 10 // Client uses these settings to discover the master key 11 var kmsProviders = new Dictionary<string, IReadOnlyDictionary<string, object>>(); 12 var localOptions = new Dictionary<string, object> 13 { 14 { "key", localMasterKeyBytes } 15 }; 16 kmsProviders.Add("local", localOptions); 17 18 // Create Data Encryption Key 19 var clientEncryption = GetClientEncryption(kmsProviders); 20 var dataKeyid = clientEncryption.CreateDataKey("local", new DataKeyOptions(), CancellationToken.None); 21 clientEncryption.Dispose(); 22 Console.WriteLine($"Local DataKeyId [UUID]: {dataKeyid}"); 23 24 var dataKeyIdBase64 = Convert.ToBase64String(GuidConverter.ToBytes(dataKeyid, GuidRepresentation.Standard)); 25 Console.WriteLine($"Local DataKeyId [base64]: {dataKeyIdBase64}"); 26 27 // Optional validation; checks that key was created successfully 28 ValidateKey(dataKeyid); 29 return dataKeyIdBase64; 30 }
Este método é o que chamamos no programa principal. É aqui que geramos nossa chave de criptografia de dados. As linhas 6a7 leem a chave mestra local do nosso arquivo
master-key.txt
e a convertem em uma array de bytes.As linhas 11a16 definem as configurações do provedor de KMS que o cliente precisa para descobrir a chave mestre. Como você pode ver, adicionamos o provedor local e a chave mestra local correspondente que acabamos de recuperar.
Com essas configurações do provedor KMS, construímos configurações adicionais de criptografia do cliente. Fazemos isso em um método separado chamado
GetClientEncryption()
. Depois de criada, finalmente geramos uma chave criptografada.Como medida adicional, chamamos um terceiro novo método
ValidateKey()
, apenas para garantir que a chave de criptografia de dados foi criada. Após essas etapas, e se for bem-sucedido, o métodoCreateKeyWithLocalKmsProvider()
retorna o ID da nossa chave de dados codificado no formato Base64.Após o método CreateKeyWithLocalKmsProvider(), adicione o seguinte método:
1 // KmsKeyHelper.cs 2 3 private ClientEncryption GetClientEncryption( 4 Dictionary<string, IReadOnlyDictionary<string, object>> kmsProviders) 5 { 6 var keyVaultClient = new MongoClient(_mdbConnectionString); 7 var clientEncryptionOptions = new ClientEncryptionOptions( 8 keyVaultClient: keyVaultClient, 9 keyVaultNamespace: _keyVaultNamespace, 10 kmsProviders: kmsProviders); 11 12 return new ClientEncryption(clientEncryptionOptions); 13 }
No método
CreateKeyWithLocalKmsProvider()
, chamamosGetClientEncryption()
(o método que acabamos de adicionar) para criar nossas configurações de criptografia de cliente. Essas configurações incluem o cliente do cofre de chaves, o namespace do cofre de chaves e os provedores de KMS a serem usados.Neste método, construímos um MongoClient usando a connection string e, em seguida, definimos como um cliente de cofre de chaves. Também usamos o namespace do cofre de chaves que foi passado e os provedores KMS locais que construímos anteriormente. Essas opções de criptografia do cliente são então retornadas.
Por último, mas não menos importante, depois de GetClientEncryption(), adicione o método final:
1 // KmsKeyHelper.cs 2 3 private void ValidateKey(Guid dataKeyId) 4 { 5 var client = new MongoClient(_mdbConnectionString); 6 var collection = client 7 .GetDatabase(_keyVaultNamespace.DatabaseNamespace.DatabaseName) 8 9 .GetCollection<BsonDocument>(_keyVaultNamespace.CollectionName, new MongoCollectionSettings { GuidRepresentation = GuidRepresentation.Standard }); 10 11 12 var query = Builders<BsonDocument>.Filter.Eq("_id", new BsonBinaryData(dataKeyId, GuidRepresentation.Standard)); 13 var keyDocument = collection 14 .Find(query) 15 .Single(); 16 17 Console.WriteLine(keyDocument); 18 }
Embora opcional, esse método verifica convenientemente se a chave de criptografia de dados foi criada corretamente. Ele faz isso construindo um MongoClient usando a connection string especificada e, em seguida, consulta o banco de dados em busca da chave de criptografia de dados. Se tivesse sido criada com sucesso, a chave de criptografia de dados teria sido inserida como um documento em seu conjunto de réplicas e seria recuperada na consulta.
Com essas mudanças, estamos prontos para gerar nossa chave de criptografia de dados. Certifique-se de salvar todos os arquivos e, em seguida, execute seu programa. Se tudo correr bem, seu console imprimirá dois DataKeyIds (UUID e64base), bem como um documento semelhante ao seguinte:
1 { 2 "_id" : CSUUID("aae4f3b4-91b6-4cef-8867-3113a6dfb27b"), 3 "keyMaterial" : Binary(0, "rcfTQLRxF1mg98/Jr7iFwXWshvAVIQY6JCswrW+4bSqvLwa8bQrc65w7+3P3k+TqFS+1Ce6FW4Epf5o/eqDyT//I73IRc+yPUoZew7TB1pyIKmxL6ABPXJDkUhvGMiwwkRABzZcU9NNpFfH+HhIXjs324FuLzylIhAmJA/gvXcuz6QSD2vFpSVTRBpNu1sq0C9eZBSBaOxxotMZAcRuqMA=="), 4 "creationDate" : ISODate("2020-11-08T17:58:36.372Z"), 5 "updateDate" : ISODate("2020-11-08T17:58:36.372Z"), 6 "status" : 0, 7 "masterKey" : { 8 "provider" : "local" 9 } 10 }
Para referência, aqui está a aparência da saída do meu console:
Se você quiser ter mais certeza, também poderá verificar seu cluster para ver se sua chave de criptografia de dados está armazenada como um documento no banco de dados de criptografia recém-criado e __keyVault coleção. Como estou me conectando com meu Atlas cluster, aqui está a aparência:
Doce! Agora que geramos uma chave de criptografia de dados, que foi criptografada com nossa chave mestre local, a próxima etapa é especificar quais campos em nosso aplicativo devem ser criptografados.
Para que a criptografia e a descriptografia automáticas no lado do cliente funcionem, é necessário definir um JSON schema que especifique quais campos devem ser criptografados, quais algoritmos de criptografia devem ser usados e o tipo BSON de cada campo.
Usando nosso aplicativo médico como exemplo, vamos planejar a criptografia dos seguintes campos:
Nome do campo | Algoritmos de criptografia | Tipo de JSON |
---|---|---|
SSN (Número de Segurança Social) | Determinístico(a) | Int |
Tipo sanguíneo | Aleatório | String |
Registros médicos | Aleatório | Array |
Seguro: número da apólice | Determinístico(a) | Int (incorporado dentro do objeto de seguro) |
Para facilitar um pouco e separar essa funcionalidade do restante do aplicativo, crie outra classe chamada
JsonSchemaCreator.cs
. Nela, adicione o seguinte código:1 // JsonSchemaCreator.cs 2 3 using MongoDB.Bson; 4 using System; 5 6 namespace EnvoyMedSys 7 { 8 public static class JsonSchemaCreator 9 { 10 private static readonly string DETERMINISTIC_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"; 11 private static readonly string RANDOM_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"; 12 13 private static BsonDocument CreateEncryptMetadata(string keyIdBase64) 14 { 15 var keyId = new BsonBinaryData(Convert.FromBase64String(keyIdBase64), BsonBinarySubType.UuidStandard); 16 return new BsonDocument("keyId", new BsonArray(new[] { keyId })); 17 } 18 19 private static BsonDocument CreateEncryptedField(string bsonType, bool isDeterministic) 20 { 21 return new BsonDocument 22 { 23 { 24 "encrypt", 25 new BsonDocument 26 { 27 { "bsonType", bsonType }, 28 { "algorithm", isDeterministic ? DETERMINISTIC_ENCRYPTION_TYPE : RANDOM_ENCRYPTION_TYPE} 29 } 30 } 31 }; 32 } 33 34 public static BsonDocument CreateJsonSchema(string keyId) 35 { 36 return new BsonDocument 37 { 38 { "bsonType", "object" }, 39 { "encryptMetadata", CreateEncryptMetadata(keyId) }, 40 { 41 "properties", 42 new BsonDocument 43 { 44 { "ssn", CreateEncryptedField("int", true) }, 45 { "bloodType", CreateEncryptedField("string", false) }, 46 { "medicalRecords", CreateEncryptedField("array", false) }, 47 { 48 "insurance", 49 new BsonDocument 50 { 51 { "bsonType", "object" }, 52 { 53 "properties", 54 new BsonDocument 55 { 56 { "policyNumber", CreateEncryptedField("int", true) } 57 } 58 } 59 } 60 } 61 } 62 } 63 }; 64 } 65 } 66 }
Como antes, vamos percorrer cada linha:
Primeiro, criamos duas variáveis estáticas para manter nossos tipos de criptografia. Usamos a criptografia
Deterministic
para campos que podem ser consultados e têm alta cardinalidade. Usamos a criptografiaRandom
para campos que não planejamos consultar, que têm baixa cardinalidade ou que são campos de matriz.Em seguida, criamos um método auxiliar
CreateEncryptMetadata()
. Isso retornará umBsonDocument
que contém nossa chave de dados convertida. Usaremos esta chave no métodoCreateJsonSchema()
.As linhas 19-32 compõem outro método auxiliar chamado
CreateEncryptedField()
.Isso gera oBsonDocument
adequado necessário para definir nossos campos criptografados. Ele produzirá um BsonDocument
que se assemelha ao seguinte:1 "ssn": { 2 "encrypt": { 3 "bsonType": "int", 4 "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 5 } 6 }
Finalmente, o método
CreateJsonSchema()
. Aqui, geramos o esquema completo que nosso aplicativo usará para saber quais campos criptografar e descriptografar. Este método também retorna um BsonDocument
.Algumas coisas a serem observadas sobre este esquema:
Colocar a chave
encryptMetadata
na raiz do nosso esquema nos permite criptografar todos os campos com uma única chave de dados. É aqui que você vê a chamada para nosso método auxiliarCreateEncryptMetadata()
.Dentro da chave
properties
Go todos os campos que desejamos criptografar. Portanto, para nossos camposssn
, bloodType
, medicalRecords
e insurance.policyNumber
, geramos as respectivas especificaçõesBsonDocument
necessárias usando nosso método auxiliarCreateEncryptedField()
.Com nossos campos criptografados definidos e as chaves de criptografia necessárias geradas, podemos agora ativar a criptografia no nível do campo do lado do cliente em nosso cliente MongoDB!
Não se esqueça de fazer uma pausa! ☕️ São muitas informações para absorver, então não se apresse. Certifique-se de salvar todos os seus arquivos, depois pegue um café, espreguice-se e afaste-se do computador. Este tutorial estará aqui esperando quando você estiver pronto. :)
Um
MongoClient
habilitado para CSFLE não é muito diferente de um cliente padrão. Para criar um cliente com criptografia automática, nós o instanciamos com algumas opções adicionais de criptografia automática.Como antes, vamos criar uma classe separada para manter essa funcionalidade. Crie um arquivo chamado
AutoEncryptHelper.cs
e adicione o seguinte código (observe que, como ele é um pouco mais longo do que os outros trechos de código, optei por adicionar comentários em linha para explicar o que está acontecendo em vez de esperar até depois do bloco de código):1 // AutoEncryptHelper.cs 2 3 using System; 4 using System.Collections.Generic; 5 using System.IO; 6 using MongoDB.Bson; 7 using MongoDB.Driver; 8 using MongoDB.Driver.Encryption; 9 10 namespace EnvoyMedSys 11 { 12 public class AutoEncryptHelper 13 { 14 private static readonly string __localMasterKeyPath = "../../../master-key.txt"; 15 16 // Most of what follows are sample fields and a sample medical record we'll be using soon. 17 private static readonly string __sampleNameValue = "Takeshi Kovacs"; 18 private static readonly int __sampleSsnValue = 213238414; 19 20 private static readonly BsonDocument __sampleDocFields = 21 new BsonDocument 22 { 23 { "name", __sampleNameValue }, 24 { "ssn", __sampleSsnValue }, 25 { "bloodType", "AB-" }, 26 { 27 "medicalRecords", 28 new BsonArray(new [] 29 { 30 new BsonDocument("weight", 180), 31 new BsonDocument("bloodPressure", "120/80") 32 }) 33 }, 34 { 35 "insurance", 36 new BsonDocument 37 { 38 { "policyNumber", 211241 }, 39 { "provider", "EnvoyHealth" } 40 } 41 } 42 }; 43 44 // Scaffolding of some private variables we'll need. 45 private readonly string _connectionString; 46 private readonly CollectionNamespace _keyVaultNamespace; 47 private readonly CollectionNamespace _medicalRecordsNamespace; 48 49 // Constructor that will allow us to specify our auto-encrypting 50 // client settings. This also makes it a bit easier to extend and 51 // use with a remote KMS provider later on. 52 public AutoEncryptHelper(string connectionString, CollectionNamespace keyVaultNamespace) 53 { 54 _connectionString = connectionString; 55 _keyVaultNamespace = keyVaultNamespace; 56 _medicalRecordsNamespace = CollectionNamespace.FromFullName("medicalRecords.patients"); 57 } 58 59 // The star of the show. Accepts a key location, 60 // a key vault namespace, and a schema; all needed 61 // to construct our CSFLE-enabled MongoClient. 62 private IMongoClient CreateAutoEncryptingClient( 63 KmsKeyLocation kmsKeyLocation, 64 CollectionNamespace keyVaultNamespace, 65 BsonDocument schema) 66 { 67 var kmsProviders = new Dictionary<string, IReadOnlyDictionary<string, object>>(); 68 69 // Specify the local master encryption key 70 if (kmsKeyLocation == KmsKeyLocation.Local) 71 { 72 var localMasterKeyBase64 = File.ReadAllText(__localMasterKeyPath); 73 var localMasterKeyBytes = Convert.FromBase64String(localMasterKeyBase64); 74 var localOptions = new Dictionary<string, object> 75 { 76 { "key", localMasterKeyBytes } 77 }; 78 kmsProviders.Add("local", localOptions); 79 } 80 81 // Because we didn't explicitly specify the collection our 82 // JSON schema applies to, we assign it here. This will map it 83 // to a database called medicalRecords and a collection called 84 // patients. 85 var schemaMap = new Dictionary<string, BsonDocument>(); 86 schemaMap.Add(_medicalRecordsNamespace.ToString(), schema); 87 88 // Specify location of mongocryptd binary, if necessary. 89 // Not required if path to the mongocryptd.exe executable 90 // has been added to your PATH variables 91 var extraOptions = new Dictionary<string, object>() 92 { 93 // Optionally uncomment the following line if you are running mongocryptd manually 94 // { "mongocryptdBypassSpawn", true } 95 }; 96 97 // Create CSFLE-enabled MongoClient 98 // The addition of the automatic encryption settings are what 99 // transform this from a standard MongoClient to a CSFLE-enabled 100 // one 101 var clientSettings = MongoClientSettings.FromConnectionString(_connectionString); 102 var autoEncryptionOptions = new AutoEncryptionOptions( 103 keyVaultNamespace: keyVaultNamespace, 104 kmsProviders: kmsProviders, 105 schemaMap: schemaMap, 106 extraOptions: extraOptions); 107 clientSettings.AutoEncryptionOptions = autoEncryptionOptions; 108 return new MongoClient(clientSettings); 109 } 110 } 111 }
Tudo bem, estamos quase terminando. Não se lembre de salvar o que você tem até agora! Em nossa próxima (e última) etapa, podemos finalmente experimentar a criptografia no nível do campo do lado do cliente com algumas queries!
🌟 Sabe de que programa esse paciente é? Deixe-me saber sua credibilidade como nerd (e vamos ser amigos, colegas fãs!) em um tweet!
Lembra-se dos dados de amostra que preparamos? Vamos fazer bom uso deles! Para testar uma gravação e leitura criptografada desses dados, vamos adicionar outro método à classe
AutoEncryptHelper
. Logo após o construtor, adicione o seguinte método:1 // AutoEncryptHelper.cs 2 3 public async void EncryptedWriteAndReadAsync(string keyIdBase64, KmsKeyLocation kmsKeyLocation) 4 { 5 // Construct a JSON Schema 6 var schema = JsonSchemaCreator.CreateJsonSchema(keyIdBase64); 7 8 // Construct an auto-encrypting client 9 var autoEncryptingClient = CreateAutoEncryptingClient( 10 kmsKeyLocation, 11 _keyVaultNamespace, 12 schema); 13 14 var collection = autoEncryptingClient 15 .GetDatabase(_medicalRecordsNamespace.DatabaseNamespace.DatabaseName) 16 .GetCollection<BsonDocument>(_medicalRecordsNamespace.CollectionName); 17 18 var ssnQuery = Builders<BsonDocument>.Filter.Eq("ssn", __sampleSsnValue); 19 20 // Upsert (update document if found, otherwise create it) a document into the collection 21 var medicalRecordUpdateResult = await collection 22 .UpdateOneAsync(ssnQuery, new BsonDocument("$set", __sampleDocFields), new UpdateOptions() { IsUpsert = true }); 23 24 if (!medicalRecordUpdateResult.UpsertedId.IsBsonNull) 25 { 26 Console.WriteLine("Successfully upserted the sample document!"); 27 } 28 29 // Query by SSN field with auto-encrypting client 30 var result = collection.Find(ssnQuery).Single(); 31 32 Console.WriteLine($"Encrypted client query by the SSN (deterministically-encrypted) field:\n {result}\n"); 33 }
O que está acontecendo aqui? Primeiro, usamos a
JsonSchemaCreator
classe para construir nosso esquema. Em seguida, criamos um cliente de criptografia automática usando o CreateAutoEncryptingClient()
método . Em seguida, as linhas 14a16 definem o banco de dados e a collection em funcionamento com os quais interagiremos. Por fim,alteramos um prontuário médico usando nossos dados de amostra e, em seguida, os recuperamos com o cliente de criptografia automática.Antes de inserir esse novo registro de paciente, o cliente habilitado para CSFLE criptografa automaticamente os campos apropriados, conforme estabelecido em nosso JSON schema.
Se você curte diagramas, veja o que está rolando:
Ao recuperar os dados do doente, eles são descriptografados pelo cliente. A melhor parte de ativar o CSFLE em seu aplicativo é que as queries não mudam, o que significa que os métodos do driver com os quais você já está familiarizado ainda podem ser usados.
Para o pessoal do diagrama:
Para ver isso em ação, basta modificar ligeiramente o programa principal para podermos chamar o método
EncryptedWriteAndReadAsync()
.De volta ao arquivo
Program.cs
, adicione o seguinte código:1 // Program.cs 2 3 using System; 4 using System.IO; 5 using MongoDB.Driver; 6 7 namespace EnvoyMedSys 8 { 9 public enum KmsKeyLocation 10 { 11 Local, 12 } 13 14 class Program 15 { 16 public static void Main() 17 { 18 var connectionString = "PASTE YOUR MONGODB CONNECTION STRING/ATLAS URI HERE"; 19 var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); 20 21 var kmsKeyHelper = new KmsKeyHelper( 22 connectionString: connectionString, 23 keyVaultNamespace: keyVaultNamespace); 24 var autoEncryptHelper = new AutoEncryptHelper( 25 connectionString: connectionString, 26 keyVaultNamespace: keyVaultNamespace); 27 28 string kmsKeyIdBase64; 29 30 // Ensure GenerateLocalMasterKey() only runs once! 31 if (!File.Exists("../../../master-key.txt")) 32 { 33 kmsKeyHelper.GenerateLocalMasterKey(); 34 } 35 36 kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithLocalKmsProvider(); 37 autoEncryptHelper.EncryptedWriteAndReadAsync(kmsKeyIdBase64, KmsKeyLocation.Local); 38 39 Console.ReadKey(); 40 } 41 } 42 }
Tudo bem, é isso! Salve seus arquivos e execute seu programa. Após uma breve espera, você deverá ver a seguinte saída do console:
Funciona! A saída do console que você vê foi descriptografada corretamente pelo nosso MongoClient habilitado para CSFLE. Também podemos verificar se este registro do paciente foi salvo corretamente em nosso banco de dados. Fazendo login no meu cluster Atlas, vejo o registro do paciente de Takeshi armazenado com segurança, com os campos especificados criptografados:
Para ver o desempenho dessas queries ao usar um cliente não criptografado, vamos adicionar mais um método à classe
AutoEncryptHelper
. Logo após o métodoEncryptedWriteAndReadAsync()
, adicione o seguinte:1 // AutoEncryptHelper.cs 2 3 public void QueryWithNonEncryptedClient() 4 { 5 var nonAutoEncryptingClient = new MongoClient(_connectionString); 6 var collection = nonAutoEncryptingClient 7 .GetDatabase(_medicalRecordsNamespace.DatabaseNamespace.DatabaseName) 8 .GetCollection<BsonDocument>(_medicalRecordsNamespace.CollectionName); 9 var ssnQuery = Builders<BsonDocument>.Filter.Eq("ssn", __sampleSsnValue); 10 11 var result = collection.Find(ssnQuery).FirstOrDefault(); 12 if (result != null) 13 { 14 throw new Exception("Expected no document to be found but one was found."); 15 } 16 17 // Query by name field with a normal non-auto-encrypting client 18 var nameQuery = Builders<BsonDocument>.Filter.Eq("name", __sampleNameValue); 19 result = collection.Find(nameQuery).FirstOrDefault(); 20 if (result == null) 21 { 22 throw new Exception("Expected the document to be found but none was found."); 23 } 24 25 Console.WriteLine($"Query by name (non-encrypted field) using non-auto-encrypting client returned:\n {result}\n"); 26 }
Aqui, instanciamos um MongoClient padrão sem configurações de criptografia automática. Observe que consultamos o campo
name
não criptografado ; isso ocorre porque não podemos consultar campos criptografados usando um MongoClient sem o CSFLE ativado.Por fim, adicione uma chamada para este novo método no arquivo
Program.cs
:1 // Program.cs 2 3 // Comparison query on non-encrypting client 4 autoEncryptHelper.QueryWithNonEncryptedClient();
Salve todos os seus arquivos e execute o programa novamente. Você verá que sua última consulta retorna um registro criptografado do paciente, conforme o esperado. Como estamos usando um MongoClient não habilitado para CSFLE, nenhuma descriptografia acontece, deixando apenas os campos não criptografados legíveis para nós:
Parabéns! Você chegou até aqui!
Este tutorial mostrou a você:
- Criando um aplicativo de console .NET Core.
- Instalar dependências necessárias para habilitar a criptografia em nível de campo do lado do cliente para seu aplicativo .NET core.
- Criando uma chave mestra local.
- Criando uma chave de criptografia de dados.
- Construção de um JSON schema para estabelecer quais campos criptografar.
- Configurando um MongoClient habilitado para CSFLE.
- Realizar leitura e escrita criptografada de uma amostra do registro de um cliente.
- Realizando uma leitura usando um MongoClient não habilitado para CSFLE para ver a diferença nos dados recuperados.
Com esse conhecimento de criptografia em nível de campo do lado do cliente, você poderá proteger melhor os aplicativos e entender como ele funciona!
Esperemos que este tutorial tenha simplificado a integração da criptografia em nível de campo do lado do cliente em seu aplicativo .NET! Se você tiver mais dúvidas ou estiver preso em algo,acesse os Fóruns daMongoDB Community e inicie um tópico. Uma comunidade inteira de engenheiros do MongoDB (incluindo a equipe DevRel) e outros desenvolvedores com certeza ajudarão!
Caso você queira aprender um pouco mais, aqui estão os recursos que foram cruciais para me ajudar a escrever este tutorial: