Chiffrement au niveau de l'application: Memorystore pour Redis

Ce document donne aux utilisateurs Redis sur Google Cloud un aperçu des méthodes de chiffrement permettant de limiter les attaques malveillantes sans sacrifier la vitesse et la flexibilité de Redis. Ce document décrit spécifiquement comment aider à sécuriser les données sensibles en mettant en œuvre le chiffrement encapsulé à l'aide de Memorystore et Cloud Key Management Service (Cloud KMS) : une approche de la sécurité qui offre un impact minimal sur les performances et offre une flexibilité avec la rotation des clés.

Dans le tutoriel associé à ce document, vous allez appliquer cette approche en créant une application qui communique avec l'API Cloud Key Management Service afin de chiffrer le contenu. stocké dans un magasin Memorystore pour Redis.

Ce document est destiné aux développeurs d'applications et aux professionnels de la sécurité. Il part du principe que vous possédez des connaissances de base sur la programmation Java et les concepts de sécurité. Tous les extraits de code de ce document sont rédigés en Java.

Memorystore pour Redis

Memorystore pour Redis offre un service entièrement géré, fourni par le datastore en mémoire de Redis pour créer des caches d'application pour un accès aux données inférieur à une milliseconde.

Le modèle de sécurité de Redis repose sur la conservation de ses instances dans un environnement auquel seuls les clients de confiance peuvent accéder directement. Ce modèle de sécurité ne met en œuvre que les concepts de sécurité de base, tels que le verrouillage de l'accès aux interfaces réseau, l'authentification de base et l'obscurcissement des noms de commandes. Les adresses IP privées et la gestion de l'authentification et des accès (IAM) fournissent une sécurité et une protection supplémentaires pour les instances Redis. Les instances standards à disponibilité élevée sont toujours répliquées sur plusieurs zones avec des contrats de niveau de service garantissant une disponibilité de 99,9 %.

Cas d'utilisation de Redis pour un accès aux données inférieur à une milliseconde

Redis propose de nombreuses applications pour les architectures de sécurité modernes. Pour de nombreuses entreprises, une architecture de sécurité de base suffit à répondre à la plupart des exigences de sécurité. Cependant, certains secteurs présentent des exigences de sécurité plus élevées.

Prenons l'exemple d'une application de machine learning dans le secteur des services financiers. Les transactions financières contiennent des informations sur le compte qui doivent être mises en cache pour obtenir un inférence à faible latence. En raison des exigences de faible latence, les données ne peuvent pas être tokenisées, masquées ou anonymisées. Par conséquent, il est important de créer des couches de sécurité supplémentaires pour protéger les données et réduire le risque d'exposition, même à partir de vecteurs d'attaque peu probables.

Éléments à prendre en compte pour le chiffrement encapsulé

Le concept général de chiffrement encapsulé consiste à combiner le chiffrement dans des couches. Dans ce processus, vous chiffrez des données avec une ou plusieurs clés de chiffrement de données (DEK, Data Encryption Key). Vous devez ensuite chiffrer les DEK (et non les données brutes) avec une clé de chiffrement de clés (KEK, Key Encryption Key). Le diagramme suivant illustre ce processus.

Architecture du chiffrement encapsulé.

Le chiffrement encapsulé simplifie le processus de gestion du chiffrement des données à grande échelle. Il réduit également la charge de rotation de clés, car vous n'alternez que la KEK, et non la DEK. Dans cette approche, vous chiffrez les données brutes une seule fois à l'aide de la DEK. Le rechiffrement de la DEK est soumis à une charge de traitement bien inférieure à celle du rechiffrement intégral de la base de données Redis. Si vous actualisez la clé DEK pendant le cycle de rotation, vous devez traiter à nouveau l'intégralité de l'ensemble de données chiffré. Toutefois, avec le chiffrement encapsulé, vous appliquez les bonnes pratiques de rotation des clés tout en réduisant la surcharge de traitement d'un corpus entier de données chiffrées.

Les sections suivantes décrivent (avec les extraits de code) la manière dont vous pouvez mettre en œuvre le chiffrement encapsulé à l'aide de Cloud KMS, de la bibliothèque Tink et de Memorystore. Les exemples de code de ce document proviennent du dépôt Git pour l'application tinkCryptoHelper.

Gestion des clés avec Tink et Redis

Pour le chiffrement au niveau de la couche d'application, Google fournit Cloud Key Management Service (Cloud KMS), qui vous permet de créer, d'importer et de gérer des clés cryptographiques et d'effectuer des opérations de chiffrement dans un service cloud centralisé. Vous pouvez utiliser ces clés pour effectuer des opérations de chiffrement en utilisant directement Cloud KMS, Cloud HSM ou Cloud External Key Manager (Cloud EKM), ou en utilisant des intégrations de clés de chiffrement gérées par le client (CMEK) dans d'autres services Google Cloud.

Cloud KMS est conçu autour d'une couche hiérarchique composée de trousseaux de clés contenant des clés. Chaque clé peut avoir plusieurs versions, dont l'une avec l'attribut primary. Les trousseaux de clés sont attribués à une région et font partie d'un projet dont ils héritent des autorisations.

Une clé est traitée via un URI qui reflète cette hiérarchie, par exemple dans le modèle suivant:

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

Les variables de ce modèle sont définies comme suit:

Si vous avez d'autres exigences de conformité, Google Cloud propose également Cloud HSM, un service de module de sécurité matérielle (HSM, Hardware Security Module) hébergé sur le cloud, par exemple, si vous devez effectuer des opérations de chiffrement dans un environnement matériel certifié par FIPS 140-2 de niveau 3.

Par défaut, Google Cloud chiffre le contenu du client stocké au repos (à quelques exceptions mineures) à l'aide du chiffrement encapsulé et d'un service de gestion de clés interne (KMS).

Bibliothèque Tink

L'approche décrite dans ce document se sert de la bibliothèque Tink et de Cloud KMS pour gérer le chiffrement. Tink est une bibliothèque multiplate-forme multilingue qui fournit des API cryptographiques écrites par un groupe de cryptographie et d'ingénieurs en sécurité de Google. L'objectif de Tink est de fournir une API sécurisée facile à utiliser et de réduire les pièges courants de chiffrement. Pour en savoir plus sur les considérations de conception de Tink, vous pouvez écouter cette présentation Real World Crypto et afficher les diapositives associées.

Cycle de vie de la gestion des clés

Avant de commencer à chiffrer et à déchiffrer des données de travail, vous devez d'abord configurer les composants du cycle de gestion des clés pour l'application cliente Redis. Le schéma suivant présente la relation entre les clés et les données.

Les composants du cycle de vie du service de gestion des clés incluent les clés et les rôles IAM.

Dans ce cycle de vie, toutes les données stockées dans la base de données Redis sont chiffrées avec une seule DEK. La première étape du processus consiste à générer une DEK. Tink organise des ensembles de clés en collections de clés, qui sont ensuite encapsulées par un objet KeysetHandle, comme le montre l'exemple de code suivant:

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

Toutes les clés d'une collection correspondent à une seule primitive. Il est recommandé que ce document et le tutoriel associé utilisent la technologie AES en mode Galois/Counter (GCM).

Pour autoriser l'accès à la DEK, vous devez la stocker en toute sécurité. Pour ce document, vous utilisez Cloud KMS pour chiffrer la DEK avec une KEK. Dans la bibliothèque Tink, la KEK employée par Cloud KMS est une clé qui possède une version spécifique, qui appartient à un trousseau spécifique, réside dans un emplacement spécifique et est associée à un projet spécifique comme illustré par l'URI plus haut dans ce document.

Il est recommandé de créer la KEK Cloud KMS dans un projet isolé des autres ressources Google Cloud. En suivant cette pratique, vous pouvez vous assurer que seuls les utilisateurs ou les comptes de service dotés du rôle IAM Propriétaire de ce projet peuvent accéder aux clés dans Cloud KMS et déchiffrer les données. Pour connaître les bonnes pratiques en matière de gestion des autorisations et d'accès, consultez la section Séparation des tâches.

Pour accorder l'accès à la KEK dans Cloud KMS, vous devez utiliser un compte de service autorisé à effectuer des opérations de chiffrement et de déchiffrement sur la KEK. Pour en savoir plus, consultez la page Choisir les rôles IAM appropriés. Vous pouvez obtenir les identifiants du compte de service via Google Cloud Console ou via Cloud Shell à l'aide de la commande gcloud iam service-accounts keys create. Pour accéder au service de gestion des clés avec Tink, les identifiants du compte de service (kmsCredentialsFilename) sont fournis pour instancier un client Cloud KMS, comme dans l'exemple de code suivant :

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

Dans le service de gestion des clés, la collection de clés avec la DEK chiffrée par la KEK est écrite dans un fichier (dans ce cas, dans un fichier JSON sur le système de fichiers local):

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

Chiffrer et déchiffrer des données Redis

Une fois que vous avez lancé le cycle de vie de gestion des clés et stocké la DEK de manière sécurisée, l'application cliente Redis peut utiliser cette DEK pour chiffrer et déchiffrer les valeurs Redis, comme le montre le schéma suivant.

Memorystore pour Redis utilise la DEK pour effectuer des processus cryptographiques sur les données.

L'utilisation de Tink pour effectuer des opérations de chiffrement et de déchiffrement avec la DEK stockée fonctionne comme suit:

  1. La primitive Aead est extraite de l'objet KeysetHandle.
  2. La méthode encrypt (ou decrypt) est exécutée, fournissant les données à chiffrer.

L'extrait de code suivant montre le code qui exécute les fonctions de chiffrement et de déchiffrement:

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");

Les données associées (aa) transmises à la méthode Aead.encrypt (chiffrement authentifié avec données associées) associent le chiffrement à un contexte (tel que l'ID d'une d'enregistrement). Les données associées peuvent être considérées comme identiques à l'argument salt utilisé pour le chiffrement des mots de passe. Cette approche permet de garantir l'authenticité et l'intégrité des données.

Pour cette application cliente Redis, les données associées peuvent être une constante définie dans le module qui effectue le chiffrement et le déchiffrement. Une fois l'application cliente Redis démarrée, la DEK doit être chargée et déchiffrée avec la clé KEK de Cloud KMS afin de chiffrer les données à stocker dans Redis.

À l'aide de Tink, vous chargez la collection de clés DEK, qui a été créée et stockée lors du processus d'initialisation. Tink gère le déchiffrement à l'aide de la KEK fournie via l'URI de clé de Cloud KMS, comme le montre l'extrait de code suivant:

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));
}

Combiner le chiffrement avec Redis

La section précédente décrit comment utiliser Tink pour chiffrer et déchiffrer des données sans faire référence à la base de données Redis. L'intégration du chiffrement avec Redis est simple. Dans le tutoriel associé à ce document, vous utilisez Jedis comme bibliothèque Java cliente. Vous pouvez transmettre et chiffrer des paires clé/valeur en utilisant seulement quelques lignes de code. Vous pouvez appliquer ce code à tout espace de stockage de données. L'exemple de code suivant montre comment appliquer ce code dans 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;
}

Les clés restent en texte clair, et seules les valeurs sont chiffrées avec la DEK. Par conséquent, il est important de ne pas intégrer d'informations sensibles dans les clés Redis. L'un des inconvénients du chiffrement des valeurs est qu'une structure de valeurs imbriquée ne peut pas être interrogée via des fonctions Redis natives. Vous devez tenir compte de cette caractéristique lorsque vous concevez une couche d'accès aux données.

Étape suivante