Chiffrement côté client avec Tink et Cloud KMS

Cette rubrique explique comment chiffrer des données localement et les importer dans Cloud Storage avec Tink et Cloud Key Management Service (Cloud KMS). Tink est une bibliothèque de chiffrement Open Source écrite par des cryptographes et des ingénieurs en sécurité de Google.

Présentation

Le chiffrement côté client est un chiffrement effectué avant l'envoi de vos données au cloud. Lorsque vous utilisez le chiffrement côté client, vous devez créer et gérer vos clés de chiffrement, et chiffrer vos données avant de les envoyer au cloud.

Dans cette rubrique, vous allez implémenter le chiffrement encapsulé côté client avec Tink à l'aide d'une clé de chiffrement dans Cloud KMS.

Vous trouverez une version de ce tutoriel basée sur Terraform dans le dépôt GitHub kms-solutions.

Avant de commencer

  1. Créez une clé de chiffrement symétrique Cloud KMS pour le chiffrement. Notez l'URI de la clé. Vous en aurez besoin ultérieurement.
  2. Installez Tink pour une utilisation avec Cloud KMS.
  3. Créez un bucket dans Cloud Storage pour importer vos données chiffrées.

Rôles requis

Pour vous assurer que votre compte de service dispose des autorisations nécessaires pour utiliser des clés Cloud KMS avec Tink, demandez à votre administrateur d'accorder à votre compte de service le rôle IAM Chiffreur/Déchiffreur de CryptoKeys Cloud KMS (roles/cloudkms.cryptoKeyEncrypterDecrypter) sur votre clé.

Pour en savoir plus sur l'attribution de rôles, consultez Gérer l'accès aux projets, aux dossiers et aux organisations.

Votre administrateur peut également attribuer à votre compte de service les autorisations requises à l'aide de rôles personnalisés ou d'autres rôles prédéfinis.

Chiffrement encapsulé avec Tink

Dans le chiffrement encapsulé, la clé Cloud KMS agit comme une clé de chiffrement de clé (KEK, Key Encryption Key). En d'autres termes, elle sert à chiffrer les clés de chiffrement des données (DEK, Data Encryption Key), qui elles-mêmes sont utilisées pour chiffrer les données proprement dites.

Après avoir créé une KEK dans Cloud KMS, pour chiffrer chaque message, vous devez effectuer les opérations suivantes :

  1. Générez une clé de chiffrement des données (DEK) en local.
  2. Utilisez cette DEK localement pour chiffrer le message.
  3. Appelez Cloud KMS pour chiffrer (encapsuler) la DEK avec la KEK.
  4. Stockez les données chiffrées et la DEK encapsulée.

Vous n'avez pas besoin d'implémenter ce processus de chiffrement encapsulé de A à Z lorsque vous utilisez Tink.

Pour utiliser Tink pour le chiffrement encapsulé, vous devez fournir à Tink un URI de clé et des identifiants. L'URI de clé pointe vers votre KEK dans Cloud KMS, et les identifiants permettent à Tink d'utiliser la KEK. Tink génère la DEK, chiffre les données, encapsule la DEK et renvoie un texte chiffré unique représentant les données chiffrées et la DEK encapsulée.

Tink accepte le chiffrement encapsulé en Python, Java, C++ et Go à l'aide du primitif de chiffrement authentifié avec données associées (AEAD, Authenticated Encryption with Associated Data).

Associer Tink et Cloud KMS

Pour chiffrer les DEK générées par Tink avec votre KEK dans Cloud KMS, vous devez obtenir l'URI de votre KEK. Dans Cloud KMS, l'URI de la clé KEK a le format suivant :

gcp-kms://projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME/cryptoKeyVersions/KEY_VERSION

Pour savoir comment obtenir le chemin d'accès à votre clé, consultez la page Obtenir un ID de ressource Cloud KMS.

Initialiser Tink et chiffrer les données

Tink utilise des primitives, des composants cryptographiques qui gèrent les détails de ses algorithmes sous-jacents afin que vous puissiez effectuer des tâches en toute sécurité. Chaque primitive offre une API qui gère une tâche spécifique. Ici, nous utilisons AEAD. Nous allons donc utiliser la primitive AEAD Tink.

Python

Python

Pour savoir comment installer et utiliser la bibliothèque cliente pour Cloud KMS, consultez la page Bibliothèques clientes Cloud KMS.

Pour vous authentifier auprès de Cloud KMS, configurez le service Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement local.

"""A command-line utility for performing file encryption using GCS.

It is inteded for use with small files, utilizes envelope encryption and
facilitates ciphertexts stored in GCS.
"""

from absl import app
from absl import flags
from absl import logging
from google.cloud import storage

import tink
from tink import aead
from tink.integration import gcpkms


FLAGS = flags.FLAGS

flags.DEFINE_enum('mode', None, ['encrypt', 'decrypt'],
                  'The operation to perform.')
flags.DEFINE_string('kek_uri', None,
                    'The Cloud KMS URI of the key encryption key.')
flags.DEFINE_string('gcp_credential_path', None,
                    'Path to the GCP credentials JSON file.')
flags.DEFINE_string('gcp_project_id', None,
                    'The ID of the GCP project hosting the GCS blobs.')
flags.DEFINE_string('local_path', None, 'Path to the local file.')
flags.DEFINE_string('gcs_blob_path', None, 'Path to the GCS blob.')


_GCS_PATH_PREFIX = 'gs://'


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  aead.register()

  try:
    # Read the GCP credentials and setup client
    client = gcpkms.GcpKmsClient(FLAGS.kek_uri, FLAGS.gcp_credential_path)
  except tink.TinkError as e:
    logging.exception('Error creating GCP KMS client: %s', e)
    return 1

  # Create envelope AEAD primitive using AES256 GCM for encrypting the data
  try:
    remote_aead = client.get_aead(FLAGS.kek_uri)
    env_aead = aead.KmsEnvelopeAead(
        aead.aead_key_templates.AES256_GCM, remote_aead
    )
  except tink.TinkError as e:
    logging.exception('Error creating primitive: %s', e)
    return 1

  storage_client = storage.Client.from_service_account_json(
      FLAGS.gcp_credential_path)

  try:
    bucket_name, object_name = _get_bucket_and_object(FLAGS.gcs_blob_path)
  except ValueError as e:
    logging.exception('Error parsing GCS blob path: %s', e)
    return 1
  bucket = storage_client.bucket(bucket_name)
  blob = bucket.blob(object_name)
  associated_data = FLAGS.gcs_blob_path.encode('utf-8')

  if FLAGS.mode == 'encrypt':
    with open(FLAGS.local_path, 'rb') as input_file:
      output_data = env_aead.encrypt(input_file.read(), associated_data)
    blob.upload_from_string(output_data)

  elif FLAGS.mode == 'decrypt':
    ciphertext = blob.download_as_bytes()
    with open(FLAGS.local_path, 'wb') as