Tink bietet Lösungen, um eine unsachgemäße Schlüsselverwaltung zu vermeiden, die eine erhebliche Quelle von Risiken darstellt.
Schlüssel erstellen und rotieren
Nachdem Sie im vorherigen Abschnitt Ich möchte... ein Primitiv und einen Schlüsseltyp für Ihren Anwendungsfall ausgewählt haben, verwalten Sie Ihre Schlüssel mit dem externen Schlüsselverwaltungssystem (Key Management System, KMS), das Sie ausgewählt haben:
Erstellen Sie einen Schlüsselverschlüsselungsschlüssel (KEK) in Ihrem KMS, um Ihre Schlüssel zu schützen.
Rufen Sie einen Schlüssel-URI und Schlüsselanmeldedaten aus Ihrem KMS ab, die Sie an Tink übergeben können.
Verwenden Sie die APIs von Tink oder Tinkey, um ein verschlüsseltes Keyset zu generieren. Nachdem Ihre Schlüssel verschlüsselt wurden, können Sie sie an einem beliebigen Ort speichern.
Rotieren Sie Ihre Schlüssel, um eine übermäßige Wiederverwendung zu vermeiden und um sich von einem Schlüsseldiebstahl zu erholen.
Wenn Sie Schlüssel exportieren müssen, finden Sie unter Schlüsselmaterial programmatisch exportieren Informationen dazu, wie Sie dies sicher tun können.
Schritt 1: KEK im externen KMS erstellen
Erstellen Sie einen Schlüsselverschlüsselungsschlüssel (KEK) in Ihrem externen KMS. Der KEK schützt Ihre Schlüssel durch Verschlüsselung und bietet so eine zusätzliche Sicherheitsebene.
Informationen zum Erstellen eines KEK finden Sie in der KMS-spezifischen Dokumentation:
- Google Cloud KMS
- Amazon KMS
- HashiCorp Vault (derzeit nur in Go verfügbar)
Schritt 2: Schlüssel-URI und Anmeldedaten abrufen
Sie können sowohl einen Schlüssel-URI als auch Schlüsselanmeldedaten aus Ihrem KMS abrufen.
Schlüssel-URI abrufen
Für die Verwendung von KMS-Schlüsseln ist in Tink ein Uniform Resource Identifier (URI) erforderlich.
Verwenden Sie zum Erstellen dieses URI die eindeutige ID, die der KMS dem Schlüssel bei der Erstellung zuweist. Fügen Sie das entsprechende KMS-spezifische Präfix hinzu und halten Sie sich an das Format der unterstützten Schlüssel-URIs, wie in dieser Tabelle beschrieben:
| KMS | KMS-Kennzeichnerpräfix | Schlüssel-URI-Format |
|---|---|---|
| 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] |
Schlüsselanmeldedaten abrufen
Bereiten Sie die erforderlichen Anmeldedaten vor, damit Tink sich beim externen KMS authentifizieren kann.
Die genaue Form der Anmeldedaten ist KMS-spezifisch:
- Google Cloud KMS: Für Tink sind Anmeldedaten für Dienstkonten erforderlich. Dabei handelt es sich um eine JSON-Datei, die in der Google Cloud Console erstellt und heruntergeladen werden kann.
- AWS KMS: Tink erfordert eine Datei mit Anmeldedaten, die
- enthält.
- die Zugriffsschlüssel-ID in der
accessKey-Eigenschaft und - den geheimen Zugriffsschlüssel in der Eigenschaft
secretKey.
- die Zugriffsschlüssel-ID in der
- HashiCorp Vault: Tink erfordert Dienstkontotokens, die mit dem vault token create-Befehl erstellt werden können.
Wenn Sie keine Anmeldedaten angeben, versucht Tink, Standardanmeldedaten zu laden. Weitere Informationen finden Sie in der KMS-spezifischen Dokumentation:
Schritt 3: Verschlüsseltes Keyset erstellen und speichern
Verwenden Sie die APIs von Tink (für Google Cloud KMS, AWS KMS oder HashiCorp Vault) oder Tinkey, um ein Keyset zu generieren, es mit dem externen KMS zu verschlüsseln und es zu speichern.
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
Für dieses Beispiel benötigen Sie die Google Cloud KMS-Erweiterung 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() {} }
Ok
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)
Schritt 4: Schlüssel rotieren
Um die Sicherheit Ihres Systems zu gewährleisten, müssen Sie Schlüssel rotieren.
- Aktivieren Sie die automatische Schlüsselrotation in Ihrem KMS.
Legen Sie eine geeignete Häufigkeit für die Schlüsselrotation fest. Das hängt davon ab, wie vertraulich Ihre Daten sind, wie viele Nachrichten Sie verschlüsseln müssen und ob Sie die Rotation mit externen Partnern abstimmen müssen.
- Verwenden Sie für die symmetrische Verschlüsselung Schlüssel mit einer Gültigkeitsdauer von 30 bis 90 Tagen.
- Bei der asymmetrischen Verschlüsselung kann die Rotationshäufigkeit geringer sein, aber nur, wenn Sie Schlüssel sicher widerrufen können.
Weitere Informationen zur Schlüsselrotation finden Sie in der KMS-spezifischen Dokumentation: