مدیریت کلیدها

Tink راه‌حل‌هایی برای جلوگیری از مدیریت نامناسب کلید ارائه می‌دهد که منبع اصلی خطر است.

ایجاد و چرخش کلیدها

پس‌از انتخاب نوع کلید و اولیه برای مورد استفاده‌تان (در بخش می‌خواهم… در بالا)، کلیدهایتان را با سیستم مدیریت کلید (KMS) خارجی‌ای که انتخاب کرده‌اید مدیریت کنید:

  1. کلید رمزگذاری کلید (KEK) در KMS خود ایجاد کنید تا از کلیدهایتان محافظت کنید.

  2. URI کلید و اعتبارنامه کلید را از KMS خود بازیابی کنید تا به Tink ارسال شود.

  3. از Tinkey یا میاناهای برنامه‌سازی کاربردی Tink برای تولید مجموعه کلید رمزگذاری‌شده استفاده کنید. پس‌از رمزگذاری کلیدها، می‌توانید آن‌ها را هرجا که می‌خواهید ذخیره کنید.

  4. کلیدهایتان را بچرخانید تا از استفاده مجدد گسترده از کلیدهایتان جلوگیری کنید و درصورت لو رفتن کلید، آن را بازیابی کنید.

اگر لازم است کلیدها را صادر کنید، برای جزئیات مربوط به انجام ایمن این کار، به صادر کردن برنامه‌نویسی‌شده مواد کلید مراجعه کنید.

مرحله ۱: ایجاد KEK در KMS خارجی

یک کلید رمزگذاری کلید (KEK) در KMS خارجی خود ایجاد کنید. کلید رمزگذاری کلید با رمزگذاری کلیدهایتان از آن‌ها محافظت می‌کند و لایه امنیتی اضافه‌ای ایجاد می‌کند.

برای ایجاد KEK، به اسناد مختص KMS مراجعه کنید:

مرحله ۲: دریافت شناسه منبع یکسان کلید و اطلاعات اعتباری

می‌توانید هم شناسه منبع یکسان کلید و هم اعتبارنامه کلید را از «سیستم مدیریت کلید» خود بازیابی کنید.

دریافت URI کلید

Tink برای کار با کلیدهای KMS به «شناسه منبع یکسان» (URI) نیاز دارد.

برای ساختن این URI، از شناسه یکتایی استفاده کنید که KMS به کلید اختصاص می‌دهد وقتی ایجاد می‌شود. پیشوند مناسب مختص KMS را اضافه کنید و از قالب شناسه منبع یکسان کلید پشتیبانی‌شده که در این جدول شرح داده شده است پیروی کنید:

KMS پیشوند شناسه KMS قالب URI کلید
AWS KMS aws-kms:// aws-kms://arn:aws:kms:[region]:[account-id]:key/[key-id]
GCP KMS gcp-kms:// gcp-kms://projects/*/locations/*/keyRings/*/cryptoKeys/*
HashiCorp Vault hcvault:// hcvault://[key-id]

دریافت اطلاعات اعتباری کلید

اعتبارنامه‌های لازم را آماده کنید تا Tink بتواند در KMS خارجی اصالت‌سنجی کند.

شکل دقیق اعتبارنامه مختص KMS است:

اگر اطلاعات اعتباری را ارائه ندهید، Tink سعی می‌کند اطلاعات اعتباری پیش‌فرض را بار کند. برای اطلاعات بیشتر، به اسناد خاص KMS مراجعه کنید:

مرحله ۳: ایجاد و ذخیره کردن مجموعه کلید رمزگذاری‌شده

از میاناهای برنامه‌سازی کاربردی Tink (برای Google Cloud KMS،‏ AWS KMS، یا HashiCorp Vault) یا Tinkey برای تولید مجموعه کلید استفاده کنید، آن را بااستفاده از KMS خارجی رمزگذاری کنید، و آن را در جایی ذخیره کنید.

Tinkey

tinkey create-keyset --key-template AES128_GCM \
  --out-format json --out encrypted_aead_keyset.json \
  --master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar \
  --credential gcp_credentials.json

جاوا

برای این مثال، به افزونه Google Cloud KMS نیاز دارید tink-java-gcpkms.

package encryptedkeyset;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.aead.AeadConfig;
import com.google.crypto.tink.aead.PredefinedAeadParameters;
import com.google.crypto.tink.integration.gcpkms.GcpKmsClient;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * A command-line utility for working with encrypted keysets.
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>mode: Can be "generate", "encrypt" or "decrypt". If mode is "generate", it will generate a
 *       keyset, encrypt it and store it in the key-file argument. If mode is "encrypt" or
 *       "decrypt", it will read and decrypt an keyset from the key-file argument, and use it to
 *       encrypt or decrypt the input-file argument.
 *   <li>kek-uri: Use this Cloud KMS' key as the key-encrypting-key for envelope encryption.
 *   <li>gcp-credential-file: Use this JSON credential file to connect to Cloud KMS.
 *   <li>input-file: If mode is "encrypt" or "decrypt", read the input from this file.
 *   <li>output-file: If mode is "encrypt" or "decrypt", write the result to this file.
 */
public final class EncryptedKeysetExample {
  private static final String MODE_ENCRYPT = "encrypt";
  private static final String MODE_DECRYPT = "decrypt";
  private static final String MODE_GENERATE = "generate";
  private static final byte[] EMPTY_ASSOCIATED_DATA = new byte[0];

  public static void main(String[] args) throws Exception {
    if (args.length != 4 && args.length != 6) {
      System.err.printf("Expected 4 or 6 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java EncryptedKeysetExample generate/encrypt/decrypt key-file kek-uri"
              + " gcp-credential-file input-file output-file");
      System.exit(1);
    }
    String mode = args[0];
    if (!mode.equals(MODE_ENCRYPT) && !mode.equals(MODE_DECRYPT) && !mode.equals(MODE_GENERATE)) {
      System.err.print("The first argument should be either encrypt, decrypt or generate");
      System.exit(1);
    }
    Path keyFile = Paths.get(args[1]);
    String kekUri = args[2];
    String gcpCredentialFilename = args[3];

    // Initialise Tink: register all AEAD key types with the Tink runtime
    AeadConfig.register();

    // From the key-encryption key (KEK) URI, create a remote AEAD primitive for encrypting Tink
    // keysets.
    Aead kekAead = new GcpKmsClient().withCredentials(gcpCredentialFilename).getAead(kekUri);

    if (mode.equals(MODE_GENERATE)) {
      KeysetHandle handle = KeysetHandle.generateNew(PredefinedAeadParameters.AES128_GCM);

      String serializedEncryptedKeyset =
          TinkJsonProtoKeysetFormat.serializeEncryptedKeyset(
              handle, kekAead, EMPTY_ASSOCIATED_DATA);
      Files.write(keyFile, serializedEncryptedKeyset.getBytes(UTF_8));
      return;
    }

    // Use the primitive to encrypt/decrypt files

    // Read the encrypted keyset
    KeysetHandle handle =
        TinkJsonProtoKeysetFormat.parseEncryptedKeyset(
            new String(Files.readAllBytes(keyFile), UTF_8), kekAead, EMPTY_ASSOCIATED_DATA);

    // Get the primitive
    Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class);

    Path inputFile = Paths.get(args[4]);
    Path outputFile = Paths.get(args[5]);

    if (mode.equals(MODE_ENCRYPT)) {
      byte[] plaintext = Files.readAllBytes(inputFile);
      byte[] ciphertext = aead.encrypt(plaintext, EMPTY_ASSOCIATED_DATA);
      Files.write(outputFile, ciphertext);
    } else if (mode.equals(MODE_DECRYPT)) {
      byte[] ciphertext = Files.readAllBytes(inputFile);
      byte[] plaintext = aead.decrypt(ciphertext, EMPTY_ASSOCIATED_DATA);
      Files.write(outputFile, plaintext);
    }
  }

  private EncryptedKeysetExample() {}
}

رفتن

import (
	"bytes"
	"fmt"
	"log"

	"github.com/tink-crypto/tink-go/v2/aead"
	"github.com/tink-crypto/tink-go/v2/keyset"
	"github.com/tink-crypto/tink-go/v2/testing/fakekms"
)

// The fake KMS should only be used in tests. It is not secure.
const keyURI = "fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE"

func Example_encryptedKeyset() {
	// Get a KEK (key encryption key) AEAD. This is usually a remote AEAD to a KMS. In this example,
	// we use a fake KMS to avoid making RPCs.
	client, err := fakekms.NewClient(keyURI)
	if err != nil {
		log.Fatal(err)
	}
	kekAEAD, err := client.GetAEAD(keyURI)
	if err != nil {
		log.Fatal(err)
	}

	// Generate a new keyset handle for the primitive we want to use.
	newHandle, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
	if err != nil {
		log.Fatal(err)
	}

	// Choose some associated data. This is the context in which the keyset will be used.
	keysetAssociatedData := []byte("keyset encryption example")

	// Encrypt the keyset with the KEK AEAD and the associated data.
	buf := new(bytes.Buffer)
	writer := keyset.NewBinaryWriter(buf)
	err = newHandle.WriteWithAssociatedData(writer, kekAEAD, keysetAssociatedData)
	if err != nil {
		log.Fatal(err)
	}
	encryptedKeyset := buf.Bytes()

	// The encrypted keyset can now be stored.

	// To use the primitive, we first need to decrypt the keyset. We use the same
	// KEK AEAD and the same associated data that we used to encrypt it.
	reader := keyset.NewBinaryReader(bytes.NewReader(encryptedKeyset))
	handle, err := keyset.ReadWithAssociatedData(reader, kekAEAD, keysetAssociatedData)
	if err != nil {
		log.Fatal(err)
	}

	// Get the primitive.
	primitive, err := aead.New(handle)
	if err != nil {
		log.Fatal(err)
	}

	// Use the primitive.
	plaintext := []byte("message")
	associatedData := []byte("example encryption")
	ciphertext, err := primitive.Encrypt(plaintext, associatedData)
	if err != nil {
		log.Fatal(err)
	}
	decrypted, err := primitive.Decrypt(ciphertext, associatedData)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(decrypted))
	// Output: message
}

Python

"""A command-line utility for generating, encrypting and storing keysets."""

from absl import app
from absl import flags
from absl import logging

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


FLAGS = flags.FLAGS

flags.DEFINE_enum('mode', None, ['generate', 'encrypt', 'decrypt'],
                  'The operation to perform.')
flags.DEFINE_string('keyset_path', None,
                    'Path to the keyset used for encryption.')
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('input_path', None, 'Path to the input file.')
flags.DEFINE_string('output_path', None, 'Path to the output file.')
flags.DEFINE_string('associated_data', None,
                    'Optional associated data to use with the '
                    'encryption operation.')


def main(argv):
  del argv  # Unused.

  associated_data = b'' if not FLAGS.associated_data else bytes(
      FLAGS.associated_data, 'utf-8')

  # 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 AES128 GCM for encrypting the data
  try:
    remote_aead = client.get_aead(FLAGS.kek_uri)
  except tink.TinkError as e:
    logging.exception('Error creating primitive: %s', e)
    return 1

  if FLAGS.mode == 'generate':
    # Generate a new keyset
    try:
      key_template = aead.aead_key_templates.AES128_GCM
      keyset_handle = tink.new_keyset_handle(key_template)
    except tink.TinkError as e:
      logging.exception('Error creating primitive: %s', e)
      return 1

    # Encrypt the keyset_handle with the remote key-encryption key (KEK)
    with open(FLAGS.keyset_path, 'wt') as keyset_file:
      try:
        keyset_encryption_associated_data = 'encrypted keyset example'
        serialized_encrypted_keyset = (
            tink.json_proto_keyset_format.serialize_encrypted(
                keyset_handle, remote_aead, keyset_encryption_associated_data
            )
        )
        keyset_file.write(serialized_encrypted_keyset)
      except tink.TinkError as e:
        logging.exception('Error writing key: %s', e)
        return 1
    return 0

  # Use the keyset to encrypt/decrypt data

  # Read the encrypted keyset into a keyset_handle
  with open(FLAGS.keyset_path, 'rt') as keyset_file:
    try:
      serialized_encrypted_keyset = keyset_file.read()
      keyset_encryption_associated_data = 'encrypted keyset example'
      keyset_handle = tink.json_proto_keyset_format.parse_encrypted(
          serialized_encrypted_keyset,
          remote_aead,
          keyset_encryption_associated_data,
      )
    except tink.TinkError as e:
      logging.exception('Error reading key: %s', e)
      return 1

  # Get the primitive
  try:
    cipher = keyset_handle.primitive(aead.Aead)
  except tink.TinkError as e:
    logging.exception('Error creating primitive: %s', e)
    return 1

  with open(FLAGS.input_path, 'rb') as input_file:
    input_data = input_file.read()
    if FLAGS.mode == 'decrypt':
      output_data = cipher.decrypt(input_data, associated_data)
    elif FLAGS.mode == 'encrypt':
      output_data = cipher.encrypt(input_data, associated_data)
    else:
      logging.error(
          'Unsupported mode %s. Please choose "encrypt" or "decrypt".',
          FLAGS.mode,
      )
      return 1

    with open(FLAGS.output_path, 'wb') as output_file:
      output_file.write(output_data)


if __name__ == '__main__':
  flags.mark_flags_as_required([
      'mode', 'keyset_path', 'kek_uri', 'gcp_credential_path'])
  app.run(main)

مرحله ۴: چرخاندن کلیدها

برای اطمینان از امنیت سیستم، باید کلیدها را بچرخانید.

  1. چرخش خودکار کلید را در KMS خود فعال کنید.
  2. تعداد دفعات مناسبی را برای چرخش کلیدها تعیین کنید. این امر به میزان حساسیت داده‌های شما، تعداد پیام‌هایی که باید رمزگذاری کنید، و اینکه آیا باید چرخش را با شرکای خارجی هماهنگ کنید یا نه بستگی دارد.

    • برای رمزگذاری متقارن، از کلیدهای ۳۰ تا ۹۰ روزه استفاده کنید.
    • برای رمزگذاری نامتقارن، تناوب چرخش می‌تواند کمتر باشد، اما فقط درصورتی‌که بتوانید کلیدها را به‌طور ایمن لغو کنید.

درباره چرخش کلید در اسناد خاص KMS بیشتر بدانید: