Tink предлагает решения, позволяющие избежать неправильного управления ключами, которое является основным источником риска .
Создание и ротация ключей
После выбора примитива и типа ключа для вашего варианта использования (в предыдущем разделе « Я хочу... ») управляйте своими ключами с помощью выбранной вами внешней системы управления ключами (KMS) :
Создайте ключ шифрования ключа (KEK) в своем KMS, чтобы защитить свои ключи.
Получите ключевой URI и ключевые учетные данные из вашего KMS для передачи в Tink.
Используйте API-интерфейсы Tink или Tinkey для создания зашифрованного набора ключей . После того, как ваши ключи зашифрованы, вы можете хранить их где угодно.
Меняйте ключи , чтобы избежать их повторного использования и восстановиться после компрометации ключей.
Если вам нужно экспортировать ключи, см. раздел «Программный экспорт материала ключа» для получения подробной информации о том, как безопасно это сделать.
Шаг 1. Создайте ключ KEK во внешнем KMS.
Создайте ключ шифрования ключа (KEK) во внешнем KMS. KEK защищает ваши ключи, шифруя их, добавляя дополнительный уровень безопасности.
Чтобы создать ключ KEK, обратитесь к документации по KMS:
- Google Cloud KMS
- Амазон КМС
- HashiCorp Vault (в настоящее время доступно только на языке Go)
Шаг 2. Получите ключевой URI и учетные данные.
Вы можете получить как URI ключа, так и учетные данные ключа из KMS.
Получить ключевой URI
Для работы с ключами KMS Tink требуется универсальный идентификатор ресурса (URI).
Чтобы создать этот URI, используйте уникальный идентификатор, который KMS присваивает ключу при его создании . Добавьте соответствующий префикс KMS и следуйте формату поддерживаемых URI ключей, как описано в этой таблице:
КМС | Префикс идентификатора KMS | Ключевой формат URI |
---|---|---|
АВС КМС | aws-kms:// | aws-kms://arn:aws:kms:[region]:[account-id]:key/[key-id] |
GCP КМС | gcp-kms:// | gcp-kms://projects/*/locations/*/keyRings/*/cryptoKeys/* |
Хранилище ХашиКорп | hcvault:// | hcvault://[key-id] |
Получите ключевые учетные данные
Подготовьте необходимые учетные данные, чтобы Tink мог пройти аутентификацию на внешнем KMS.
Точная форма учетных данных зависит от KMS:
- Google Cloud KMS – Tink требуются учетные данные сервисных учетных записей . Это файл JSON, который можно создать и загрузить из Google Cloud Console .
- AWS KMS – Tink требуется файл учетных данных , содержащий
- идентификатор ключа доступа в свойстве
accessKey
и - секретный ключ доступа в свойстве
secretKey
.
- идентификатор ключа доступа в свойстве
- HashiCorp Vault – Tink требуются сервисные токены, которые можно создать с помощью команды создания токена хранилища .
Если вы не предоставите учетные данные, Tink попытается загрузить учетные данные по умолчанию. Для получения дополнительной информации обратитесь к документации по KMS:
Шаг 3. Создайте и сохраните зашифрованный набор ключей.
Используйте API-интерфейсы Tink (для Google Cloud KMS, AWS KMS или HashiCorp Vault) или Tinkey , чтобы сгенерировать набор ключей, зашифровать его с помощью внешнего KMS и сохранить где-нибудь.
Тинки
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.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(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 }
Питон
"""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 AES256 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)
Шаг 4. Поворот клавиш
Чтобы обеспечить безопасность вашей системы, необходимо чередовать ключи.
- Включите автоматическую ротацию ключей в вашем KMS.
Определите подходящую частоту вращения клавиш. Это зависит от того, насколько конфиденциальны ваши данные, сколько сообщений вам нужно зашифровать и нужно ли вам согласовывать ротацию с внешними партнерами.
- Для симметричного шифрования используйте ключи сроком от 30 до 90 дней.
- Для асимметричного шифрования частота ротации может быть ниже, но только в том случае, если вы можете безопасно отозвать ключи.
Дополнительные сведения о ротации ключей см. в документации по KMS: