Tink oferuje rozwiązania pozwalające uniknąć niewłaściwego zarządzania kluczami, które jest poważnym źródłem ryzyka.
Tworzenie i obracanie kluczy
Po wybraniu typu prymitywu i klucza dla danego przypadku użycia (w poprzedniej sekcji Chcę…) możesz zarządzać kluczami w wybranym systemie zarządzania kluczami (KMS):
Utwórz klucz szyfrujący klucze (KEK) w KMS, aby chronić swoje klucze.
Pobierz identyfikator URI klucza i dane klucza z usługi KMS, aby przekazać je do Tink.
Użyj interfejsów API Tink lub Tinkey, aby wygenerować zaszyfrowany zestaw kluczy. Po zaszyfrowaniu kluczy możesz je przechowywać w dowolnym miejscu.
Wykonuj rotację kluczy, aby uniknąć wielokrotnego używania tych samych kluczy i odzyskać klucze po ich przechwyceniu.
Jeśli musisz wyeksportować klucze, zapoznaj się z artykułem Programatyczne eksportowanie klucza materiału, aby dowiedzieć się, jak to zrobić bezpiecznie.
Krok 1. Utwórz klucz KEK w zewnętrznym KMS
Utwórz klucz szyfrowania klucza (KEK) w zewnętrznym systemie KMS. KEK chroni Twoje klucze, szyfrując je i dodając dodatkową warstwę zabezpieczeń.
Aby utworzyć KEK, zapoznaj się z dokumentacją KMS:
- Google Cloud KMS
- Amazon KMS
- Vault Vault (obecnie tylko w języku Go)
Krok 2. Uzyskaj identyfikator URI klucza i dane logowania
Z KMS możesz pobrać zarówno identyfikator URI klucza, jak i dane logowania klucza.
Uzyskaj identyfikator URI klucza
Aby współpracować z kluczami KMS, Tink wymaga ujednoliconego identyfikatora zasobu (URI).
Aby utworzyć ten identyfikator URI, użyj unikalnego identyfikatora przypisanego przez KMS do klucza w momencie jego utworzenia. Dodaj odpowiedni prefiks specyficzny dla KMS i stosuj format obsługiwanych identyfikatorów URI klucza zgodnie z opisem w tej tabeli:
KMS | Prefiks identyfikatora KMS | Format identyfikatora URI klucza |
---|---|---|
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/* |
Skarbiec HashiCorp | hcvault:// |
hcvault://[key-id] |
Uzyskiwanie kluczowych danych logowania
Przygotuj niezbędne dane logowania, aby Tink mógł uwierzytelnić się w zewnętrznym KMS.
Dokładna forma danych uwierzytelniających zależy od KMS:
- Google Cloud KMS – Tink wymaga danych logowania do kont usługi. Jest to plik JSON, który można utworzyć i pobrać z konsoli Google Cloud.
- AWS KMS – Tink wymaga pliku danych uwierzytelniających, który zawiera:
- identyfikator klucza dostępu w usługach
accessKey
- tajny klucz dostępu w właściwości
secretKey
.
- identyfikator klucza dostępu w usługach
- HashiCorp Vault – Tink wymaga tokenów usługi, które można utworzyć za pomocą polecenia tworzenia tokenów Vault.
Jeśli nie podasz danych logowania, Tink spróbuje wczytać domyślne dane logowania. Więcej informacji znajdziesz w dokumentacji KMS:
Krok 3. Utwórz i przechowuj zaszyfrowany kluczyk
Użyj interfejsów API Tink (dla Google Cloud KMS, AWS KMS lub HashiCorp Vault) lub Tinkey, aby wygenerować zestaw kluczy, zaszyfrować go za pomocą zewnętrznego KMS i zapisać w wybranym miejscu.
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
Java
W tym przykładzie potrzebujesz rozszerzenia Cloud KMS w Google Cloud 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() {} }
Przeczytaj
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 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)
Krok 4. Wykonaj rotację kluczy
Aby zapewnić bezpieczeństwo systemu, musisz wykonywać rotację kluczy.
- Włącz automatyczną rotację kluczy w KMS.
Określ odpowiednią częstotliwość rotacji kluczy. Zależy to od tego, jak wrażliwe są Twoje dane, ile wiadomości chcesz szyfrować i czy musisz koordynować rotację z partnerami zewnętrznymi.
- W przypadku szyfrowania symetrycznego używaj kluczy o okresie ważności 30–90 dni.
- W przypadku szyfrowania asymetrycznego częstotliwość rotacji może być mniejsza, ale tylko wtedy, gdy możesz bezpiecznie unieważnić klucze.
Więcej informacji o rotacji kluczy znajdziesz w dokumentacji KMS: