Criptografia no nível do aplicativo: Memorystore para Redis

Neste documento, os usuários do Redis no Google Cloud têm uma visão geral dos métodos de criptografia que ajudam a reduzir ataques maliciosos sem sacrificar a velocidade e a flexibilidade do Redis. Este documento descreve especificamente como ajudar a proteger dados confidenciais implementando criptografia do envelope usando Memorystore e Cloud Key Management Service (Cloud KMS) — uma abordagem de segurança que oferece impacto mínimo no desempenho e oferece flexibilidade com rotação de chaves.

No tutorial associado a este documento, você aplica essa abordagem criando um aplicativo que se comunica com a API Cloud Key Management Service para criptografar o conteúdo armazenado em um repositório do Memorystore para Redis.

Este documento é destinado a desenvolvedores de aplicativos e profissionais de segurança e pressupõe um conhecimento básico de conceitos de segurança e programação Java. Todos os snippets de código neste documento estão escritos em Java.

Memorystore para Redis

O Memorystore para Redis oferece um serviço totalmente gerenciado que é alimentado pelo armazenamento de dados na memória do Redis para criar caches de aplicativos para acesso a dados em menos de um milissegundo.

O modelo de segurança principal do Redis depende da manutenção de suas instâncias em um ambiente que somente clientes confiáveis possam acessar diretamente. Esse modelo de segurança implementa apenas conceitos básicos de segurança, como bloquear o acesso a interfaces de rede, exigir autenticação básica e ofuscar nomes de comandos. O controle de acesso baseado em papéis do gerenciamento de identidade e acesso (IAM, na sigla em inglês) e IPs particulares fornecem mais segurança e proteção para instâncias do Redis. As instâncias de alta disponibilidade padrão são sempre replicadas nas zonas e fornecem contratos de nível de serviço (SLAs, na sigla em inglês) de 99,9% de disponibilidade.

Casos de uso do Redis para acesso a dados em submilissegundos

O Redis tem vários aplicativos para arquiteturas de segurança modernas. Para muitas empresas, uma arquitetura de segurança básica é suficiente para atender à maioria dos requisitos de segurança. No entanto, alguns setores têm requisitos de segurança mais altos.

Por exemplo, pense em um aplicativo de machine learning no setor de serviços financeiros. As transações financeiras contêm detalhes da conta que precisam ser armazenados em cache para inferência de baixa latência. Devido aos requisitos de baixa latência, os dados não podem ser tokenizados, mascarados ou anonimizados. Portanto, é importante criar camadas extras de segurança para ajudar a proteger os dados e reduzir o risco de exposição, mesmo de vetores de ataque improvável.

Considerações sobre o uso da criptografia de envelope

O conceito geral de criptografia de envelope é combinar a criptografia em camadas. Nesse processo, você criptografa dados com uma ou mais chaves de criptografia de dados (DEKs, na sigla em inglês). Depois, você criptografa as DEKs (não os dados brutos) com uma chave de criptografia de chaves (KEK, na sigla em inglês). O diagrama a seguir ilustra esse processo:

Arquitetura de criptografia do envelope.

A criptografia de envelope simplifica o processo de gerenciamento da criptografia de dados em escala. Isso também reduz a sobrecarga de rotação de chaves, porque você só gira a KEK, e não a DEK. Nessa abordagem, você criptografa os dados brutos apenas uma vez, usando a DEK. Recriptografar a DEK apresenta uma carga de processamento muito menor do que a recriptografia de todo o conteúdo do banco de dados do Redis. Se você atualizar a chave de DEK durante um ciclo de rotação, será necessário reprocessar todo o conjunto de dados criptografados. No entanto, com a criptografia de envelope, você usa as práticas recomendadas de rotação de chaves e reduz a sobrecarga de reprocessar todo o corpus de dados criptografados.

As seções a seguir discutem (com snippets de código) como é possível implementar a criptografia de envelope usando o Cloud KMS, a biblioteca do Tink e o Memorystore. As amostras de código neste documento são do repositório Git para o aplicativo tinkCryptoHelper.

Gerenciamento de chaves com Tink e Redis

Para criptografia na camada do aplicativo, o Google oferece o Cloud Key Management Service (Cloud KMS), que permite criar, importar e gerenciar chaves criptográficas e executar operações de criptografia em um serviço de nuvem centralizado. É possível usar essas chaves para executar operações de criptografia usando o Cloud KMS diretamente, o Cloud HSM ou o Cloud External Key Manager (Cloud EKM) ou usando integrações de chaves de criptografia gerenciadas pelo cliente (CMEK, na sigla em inglês) nos outros serviços do Google Cloud.

O Cloud KMS foi projetado em torno de uma camada hierárquica que consiste em toques de chave que mantêm chaves. Cada chave pode ter várias versões, uma delas com o atributo primary. Os keyrings são atribuídos a uma região e fazem parte de um projeto de onde as permissões são herdadas.

Uma chave é abordada por meio de um URI que reflete essa hierarquia, por exemplo, no seguinte padrão:

gcp-kms://projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING_ID/cryptoKeys/KEY_ID

As variáveis nesse padrão são definidas da seguinte maneira:

Se você tiver outros requisitos de conformidade, o Google Cloud também oferece o Cloud HSM, um serviço de módulo de segurança de hardware (HSM, na sigla em inglês) hospedado na nuvem. Por exemplo, se você precisar executar operações criptográficas em um ambiente de hardware certificado pelo FIPS 140-2 Nível 3.

Por padrão, o Google Cloud criptografa o conteúdo do cliente armazenado em repouso, com algumas pequenas exceções, usando a criptografia de envelope com um serviço de gerenciamento de chaves (KMS) interno.

Biblioteca Tink

A abordagem descrita neste documento usa a biblioteca Tink e o Cloud KMS para gerenciar a criptografia. A Tink é uma biblioteca de várias linguagens e várias plataformas que fornece APIs criptográficas criadas por um grupo de criptógrafos e engenheiros de segurança no Google. O objetivo do Tink é fornecer uma API segura que seja simples de usar e reduza armadilhas comuns. Para saber mais detalhes sobre as considerações de design do Tink, ouça esta palestra do Real World Crypto e veja os slides que o acompanham.

O ciclo de vida do gerenciamento de chaves

Antes de começar a criptografar e descriptografar dados de trabalho, você precisa configurar os componentes do ciclo de vida de gerenciamento de chaves para o aplicativo cliente Redis. No diagrama a seguir, fornecemos uma visão geral da relação entre as chaves e os dados.

Os componentes do ciclo de vida do KMS incluem chaves e papéis do
IAM.

Nesse ciclo de vida, todos os dados armazenados no banco de dados do Redis são criptografados com uma única DEK. A primeira etapa do processo é gerar uma DEK. A Tink organiza coleções de chaves em conjuntos de chaves, que são então encapsuladas por um objeto KeysetHandle, como mostra o exemplo de código a seguir:

KeysetHandle k = KeysetHandle.generateNew
    (AeadKeyTemplates.createAesGcmKeyTemplate(256 / 8));

Todas as chaves em um conjunto de chaves correspondem a um único primitivo. Como prática recomendada, este documento e o tutorial associado usam AES em Galois/Counter Mode (GCM).

Para permitir o acesso à DEK, você precisa armazená-la com segurança. Neste documento, você usa o Cloud KMS para criptografar a DEK com uma KEK. Na biblioteca Tink, a KEK que o Cloud KMS usa é uma chave que tem uma versão específica, pertence a um keyring específico, reside em um local específico e está associada a um projeto específico, conforme ilustrado pelo URI anteriormente neste documento.

Uma prática recomendada é criar a KEK do Cloud KMS em um projeto isolado de outros recursos do Google Cloud. Ao seguir essa prática, é possível garantir que somente usuários ou contas de serviço com o papel de IAM de proprietário no projeto possam acessar as chaves no Cloud KMS e descriptografar dados. Para ver práticas recomendadas sobre o gerenciamento de permissões e acesso, consulte Separação de tarefas.

Para fornecer acesso à KEK no Cloud KMS, use uma conta de serviço que tenha permissões para executar operações de criptografia e descriptografia na KEK. Para mais informações, consulte Como escolher os papéis corretos do IAM. Consiga as credenciais da conta de serviço por meio do Console do Google Cloud ou do Cloud Shell usando o comando gcloud iam service-accounts keys create. Para acessar o KMS usando a Tink, as credenciais da conta de serviço (kmsCredentialsFilename) são fornecidas para instanciar um cliente do Cloud KMS, como a amostra de código a seguir mostra:

KmsClient kmsClient = new
    GcpKmsClient().withCredentials(kmsCredentialsFilename);

No KMS, o conjunto de chaves que tem a DEK criptografada pela KEK é gravado em um arquivo (nesse caso, a um arquivo JSON no sistema de arquivos local):

k.write(JsonKeysetWriter.withFile(new File(keysetFilename)),
    kmsClient.getAead(keyResourceIdUri));

Como criptografar e descriptografar dados do Redis

Depois de iniciar o ciclo de vida do gerenciamento de chaves e armazenar a DEK com segurança, o aplicativo cliente do Redis pode usar a DEK para criptografar e descriptografar os valores do Redis, como mostrado no diagrama a seguir.

O Memorystore para Redis usa a DEK para executar processos criptográficos em
dados.

O uso do Tink para realizar operações de criptografia e descriptografia com a DEK armazenada funciona da seguinte maneira:

  1. O primitivo Aead é recuperado do objeto KeysetHandle.
  2. O método encrypt (ou decrypt) é executado, fornecendo dados a serem criptografados.

O snippet a seguir mostra o código que executa funções de criptografia e descriptografia:

Aead aead = k.getPrimitive(Aead.class);
byte[] ciphertext = aead.encrypt(clearText.getBytes("UTF-8"), aa);
String decryptedText = new String(aead.decrypt(ciphertext, aa), "UTF-8");

Os dados associados (aa) que são passados ao método Aead.encrypt, criptografia autenticada com dados associados, vinculam a criptografia a um contexto (como o código de um registro). Os dados associados podem ser considerados iguais ao argumento salt usado na criptografia de senha. Essa abordagem ajuda a garantir a autenticidade e a integridade dos dados.

Para este aplicativo cliente Redis, os dados associados podem ser uma constante definida no módulo que realiza criptografia e descriptografia. Quando o aplicativo cliente Redis é iniciado, a DEK precisa ser carregada e descriptografada com a KEK do Cloud KMS para criptografar os dados a serem armazenados no Redis.

Usando a Tink, você carrega o conjunto de chaves DEK, criado e armazenado durante o processo de inicialização. A Tink processa a descriptografia usando a KEK fornecida pelo URI da chave do Cloud KMS, como mostrado no snippet de código a seguir:

File keyset = new File(keysetFilename);
if (keyset.exists()) {
    KeysetHandle k =
        KeysetHandle.read(JsonKeysetReader.withFile(keyset),
            kmsClient.getAead(keyResourceIdUri));

    Aead aead = k.getPrimitive(Aead.class);
    String decText = new String(aead.decrypt(ciphertext, aa));
}

Como combinar criptografia com o Redis

Na seção anterior, descrevemos o uso da Tink para criptografar e descriptografar dados sem referência ao banco de dados do Redis. A integração da criptografia com o Redis é simples. No tutorial associado a este documento, o Jedis é usado como a biblioteca Java do cliente. É possível transmitir e criptografar pares de chave-valor usando apenas algumas linhas de código. É possível aplicar esse código a qualquer armazenamento de dados. Veja na amostra de código a seguir como aplicar esse código no Redis:

void set(Iterator<Map.Entry<String, String>> kvs) throws
             NullPointerException, GeneralSecurityException, IOException {
    try (Pipeline p = jedisPool.getResource().pipelined()) {
      while (kvs.hasNext()) {
        Map.Entry<String, String> r = kvs.next();
        p.set(r.getKey(), cryptoHelper.encrypt(r.getValue()));
      }
      p.sync();
    }
}

List<String> get(String... keys) throws UnsupportedEncodingException, NullPointerException,
GeneralSecurityException, IOException {
    Jedis j = jedisPool.getResource();
    List<String> values = j.mget(keys);
    List<String> l = new ArrayList<String>();
    for (String v : values) {
      l.add(cryptoHelper.decrypt(v));
    }
    return l;
}

As chaves permanecem em texto simples e apenas os valores são criptografados com a DEK. Consequentemente, é importante não incorporar informações confidenciais às chaves do Redis. Uma desvantagem com a criptografia dos valores é que não é possível consultar uma estrutura de valor aninhada por meio de funções nativas do Redis. Você precisa considerar essa característica ao criar uma camada de acesso a dados.

A seguir