Ir para o conteúdo

Usando a criptografia de campo no MongoDB

Jean da Silva
Updated date:
6 min read

Originally written for Percona Blog in June, 2021 -  https://www.percona.com/blog/using-the-mongodb-field-encryption-feature

Um dos principais tópicos atualmente é, sem dúvida, a segurança. Na rotina, isso pode passar despercebido, mas, cedo ou mais tarde, precisamos implementar ou seguir algumas diretrizes de segurança. Hoje, vamos discutir uma delas: a Criptografia de Campo.

Introdução

Falando especificamente sobre o recurso, ele está disponível apenas a partir das versões 4.2+ do MongoDB. O MongoDB oferece dois métodos de Criptografia de Campos:

  1. Criptografia automática no nível do campo do lado do cliente
  2. Criptografia explícita (manual) no nível do campo do lado do cliente

O modo automático está disponível apenas na Enterprise Edition e no Atlas, enquanto o método manual é suportado na Community Edition pelos drivers do MongoDB e também pelo mongo shell. Este artigo utilizará o Percona Server for MongoDB (PSMDB) na versão 4.4, com autenticação habilitada, e aplicará o método manual. Como o objetivo aqui é demonstrar a funcionalidade, usaremos o mongo shell para realizar todas as operações. No entanto, para aplicações, a abordagem mais recomendada é implementar a criptografia via driver. A documentação oficial lista detalhadamente os drivers suportados para criptografia no nível de campo: https://www.mongodb.com/pt-br/docs/v5.0/core/security-client-side-encryption/#std-label-field-level-encryption-drivers  

How to:

É importante mencionar que um keyfile local é rápido de configurar, mas oferece menor segurança. Por isso, não é recomendado em ambientes de produção, pois a chave é armazenada junto com o banco de dados. Em ambientes de produção, considere utilizar um dos seguintes serviços como Serviço de Gerenciamento de Chaves (KMS):

Ao utilizar um Keyfile gerenciado localmente, o MongoDB exige que o arquivo contenha uma string codificada em base64 de 96 bytes, sem quebras de linha. Essa chave pode ser criada utilizando o exemplo abaixo:

openssl rand -hex 50 | head -c 96 | base64 | tr -d '\n' > /localkeys/client.key
chmod 600 /localkeys/client.key
chown mongod:mongod /localkeys/client.key

⚠️ - Certifique-se de salvar o keyfile em um local seguro para evitar perdê-lo. Caso contrário, não será mais possível descriptografar, portanto não ler os dados posteriormente.

Utilizaremos a opção —nodb para iniciar o shell sem conexão imediata ao banco de dados. Também utilizaremos —shell para executar o código fornecido (neste caso, o valor da string fornecida ao —eval) sem que a sessão seja encerrada automaticamente no final. Esse passo é necessário porque precisamos carregar o keyfile em um objeto que posteriormente se tornará uma propriedade da conexão com o banco de dados. No exemplo a seguir, estamos carregando o keyfile na variável de banco de dados LOCAL_KEY:

// Terminal //
shell> mongo --shell --nodb --eval "var LOCAL_KEY = cat('/localkeys/client.key')"

// Dentro do mongo shell //
mongo> LOCAL_KEY 
ODEyMTY2YmNmNDA4YWZlZWVhNTFmOTUyODk4YTJjODc1ODk0NTZiN2EzYWQwZDdjNmM4MDQ5ODUzYzRkMjlhNGZlM2UyZDVmMTNjZWQ1YjAyNjAwNzZmMmQ1ZjVkMzdi
ClientSideFieldLevelEncryptionOptions = {
    keyVaultNamespace: "encryption.__keyVault", // Nome do namespace do key vault
    kmsProviders: {
        local: {
            key: BinData(0, LOCAL_KEY) // Insere a chave local carregada anteriormente
        }
    }
};

Essa conexão também utiliza autenticação padrão com nome de usuário e senha, como pode ser observado na string URI mongodb://.../.

mongo> csfleDatabaseConnection = Mongo("mongodb://dba:secret@localhost:27017/?authSource=admin", ClientSideFieldLevelEncryptionOptions)
connection to localhost:27017
mongo > keyVault = csfleDatabaseConnection.getKeyVault();
{
    "mongo": connection to localhost: 27017,
    "keyColl": encryption.__dataKeys
}
> show dbs
admin            0.000GB
config           0.000GB
encryption       0.000GB

> use encryption
switched to db encryption

> show collections
__dataKeys
mongo> keyVault.createKey(
    "local", /* Chave do tipo Local */
    "", /* Chave mestre do cliente, usada com KMS externos */
    [ "myFirstCSFLEDataKey" ]
)
UUID("5bd46d64-3fe8-4e31-a800-219eaa1b6a85")
mongo> clientEncryption = csfleDatabaseConnection.getClientEncryption();
mongo> var csfleDB = csfleDatabaseConnection.getDB("percona");
mongo> csfleDB.getCollection("newcollection").insert({
    "_id": 1,
    "medRecNum": 1,
    "firstName": "Jose",
    "lastName": "Pereira",
    "ssn": clientEncryption.encrypt(
        UUID("47130fb5-987c-4af0-9e83-5eaf672d608b"), 
        "123-45-6789",
        "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
    ),
    "comment": "Jose Pereira's SSN encrypted."
});

WriteResult({ "nInserted" : 1 })

Perfeito! Conseguimos criptografar manualmente o campo. 🎉   Agora, você deve estar se perguntando:

“Como posso ler esse valor criptografado?”

Vamos ver como podemos fazer isso na próxima seção abaixo.

Leitura de um Campo Criptografado

Neste ponto, se nos conectarmos ao banco de dados sem passar a configuração de criptografia, não conseguiremos ler as informações.

shell#> mongo "mongodb://dba:secret@localhost:27017/?authSource=admin"
mongo> use percona
switched to db percona

mongo-shell-2> show collections
newcollection

mongo-shell-2> db.newcollection.find().pretty()
{
    "_id" : 1,
    "medRecNum" : 1,
    "firstName" : "Jose",
    "lastName" : "Pereira",
    "ssn" : BinData(6,"AkcTD7WYfErwnoNer2ctYIsCVXS2nJYpSEgYFlp8ORmZ1i9PO/RGELdm+XxZyN6+ls+KLeDu1LQFtIIJs1Bwy5AMnaA3Lf4qAfm0Nmov6Iwuqer67HV2nIQk6dIa98QFLXs="),
    "comment" : "Jose Pereira's SSN encrypted."
}
exit
shell># mongo --shell --nodb --eval "var LOCAL_KEY = cat('/localkeys/client.key')"
Percona Server for MongoDB shell version v4.4.3-5
type "help" for help
mongo> var ClientSideFieldLevelEncryptionOptions = {
    "keyVaultNamespace" : "encryption.__dataKeys",
    "kmsProviders" : {
        "local" : {
            "key" : BinData(0, LOCAL_KEY)
        }
    }
}

mongo> csfleDatabaseConnection = Mongo("mongodb://dba:secret@localhost:27017/?authSource=admin", ClientSideFieldLevelEncryptionOptions)
connection to localhost:27017

(É por isso que é importante armazenar a chave em um local seguro, pois sua codificação será usada nas rotinas de leitura e escrita com criptografia.)  

mongo> percona = csfleDatabaseConnection.getDB("percona")
mongo> newcollection = percona.getCollection("newcollection")
mongo> percona.newcollection.find().pretty()
{
    "_id" : 1,
    "medRecNum" : 1,
    "firstName" : "Jose",
    "lastName" : "Pereira",
    "ssn" : "123-45-6789",
    "comment" : "Jose Pereira's SSN encrypted."
}

Perguntas Comuns

1. Um usuário root pode ler os campos criptografados?

Não. O que determina se um usuário pode ler campos criptografados é se a conexão está carregada com a chave de criptografia usada. Sem ela, o usuário não conseguirá ler os campos, mesmo que tenha privilégios de root.

mongo localhost:4420/admin -uroot -psekret --eval "db.getSiblingDB('percona').newcollection.findOne()" --quiet
{
    "_id" : 1,
    "medRecNum" : 1,
    "firstName" : "Jose",
    "lastName" : "Pereira",
    "ssn" : BinData(6,"ArIjMQ7O9Uwanyv31U9RulQCZPt7IxoZpu6mu9ekXMRcsaMKZgkJypzwNkuY+HEOMRn3eU6BMTkM71Gm5KDqi4ERTP8ExEfRMHwuDNrDmGmb1q0QA+W7CL4iMOL6oSX79uc="),
    "comment" : "Jose Pereira's SSN encrypted."
}

2. O que acontece se eu perder a chave?

Se a chave mestre do cliente for excluída ou perdida, todas as chaves de criptografia de dados relacionadas se tornam permanentemente ilegíveis. Isso faz com que todos os valores criptografados com essas chaves de dados também se tornem permanentemente inacessíveis.

3. Como isso funciona em um ReplicaSet ou Sharded Cluster?

O mesmo comportamento da seção “Leitura de um Campo Criptografado” se aplica a essas configurações. Uma vez que um documento com um campo criptografado é inserido, ele é replicado para os nós como está: criptografado. Apenas um usuário com a chave correta poderá ler o campo.

replset:SECONDARY> use percona
switched to db percona

replset:SECONDARY> rs.secondaryOk()

replset:SECONDARY> db.getSiblingDB('percona').newcollection.findOne()
{
"_id" : 1,
"medRecNum" : 1,
"firstName" : "Jose",
"lastName" : "Pereira",
"ssn" : BinData(6,"ArIjMQ7O9Uwanyv31U9RulQCZPt7IxoZpu6mu9ekXMRcsaMKZgkJypzwNkuY+HEOMRn3eU6BMTkM71Gm5KDqi4ERTP8ExEfRMHwuDNrDmGmb1q0QA+W7CL4iMOL6oSX79uc="),
"comment" : "Jose Pereira's SSN encrypted."
}

4. Existem limitações para o uso desse recurso?

Sim, conforme descrito no manual oficial. Existem limitações relacionadas a diferentes configurações, como Shard Key, Índices Únicos, Collation, Views, entre outros. É recomendável revisar o manual antes de qualquer implementação.  

Conclusão

Mesmo com a restrição ao uso da Criptografia Automática no Lado do Cliente, o método manual e o recurso em si mostraram ser uma opção interessante, principalmente porque fortalece a segurança ao permitir criptografar campos sensíveis no MongoDB, em vez de depender de ferramentas de terceiros. Isso ajuda a reduzir possíveis brechas de segurança que ferramentas externas podem criar. E se você tiver mais perguntas, sinta-se à vontade para compartilhá-las na seção de comentários abaixo.

Até mais!

Anterior
Prós e Contras: Quando você deve ou não usar o MongoDB