Tink を使用すると、一部のデータのクライアントサイド暗号化を実行し、クラウド Key Management Service(KMS)を使用して鍵を保護できます。仕組みは次のとおりです。
- クライアントがデータ暗号鍵(DEK)を生成します。
- クライアントが DEK を使用してデータを暗号化します。
- DEK は、クラウド KMS に保存されている鍵暗号鍵(KEK)で暗号化されます。
Cloud KMS を使用すると、KEK はクラウドに安全に保存され、必要に応じてローテーション、無効化、破棄できます。
Tink がクラウド KMS と連携するために必要なものについては、鍵管理の概要をご覧ください。詳細については、 こちらをご覧ください。
Tink を使用すると、クラウド KMS でクライアントサイド暗号化を次の 2 つの方法で実行できます。
KMS エンベロープ AEAD プリミティブ(
KmsEnvelopeAead)を使用する: 暗号化ごとに、Tink は新しい AEAD DEK を作成し、クラウド KMS KEK で暗号化して、暗号テキストの一部として保存します。そのため、DEK を自分で処理する必要はありません。結果として、暗号化された DEK が含まれるため、暗号テキストが少し長くなります。さらに、暗号化または復号を行うたびに、Tink は KMS に RPC を行います(データの実際の復号または暗号化はローカルで実行されます)。別のプリミティブを使用して、クラウド KMSで鍵セットを暗号化する: AEAD 以外のプリミティブを使用でき、 クラウド KMS への 呼び出しを制限できるため、より柔軟です。
KMS エンベロープ AEAD
KmsEnvelopeAead プリミティブを使用してデータを暗号化すると、Tink は次の処理を行います。
- ランダムな DEK を生成し、それを使用してデータをローカルで暗号化します。
- KMS にリクエストを送信して、KMS KEK で DEK を暗号化します。
- KEK で暗号化された暗号化 DEK を暗号化されたデータと連結します。
DEK タイプと KEK URI はどちらも KmsEnvelopeAead 鍵で指定されます。
復号する場合、Tink は逆の処理を行います。
- KEK で暗号化された DEK 鍵を抽出します。
- KMS にリクエストを送信して、KEK で暗号化された DEK を復号します。
- DEK を使用して暗号テキストをローカルで復号します。
次の例は、KmsEnvelopeAead プリミティブの使用方法を示しています。
C++
この例では、Google Cloud KMS 拡張機能
tink-cc-gcpkms が必要です。
// A command-line utility for testing Tink Envelope AEAD with Google Cloud KMS. #include <fstream> #include <iostream> #include <memory> #include <ostream> #include <sstream> #include <string> #include <utility> #include "absl/flags/parse.h" #include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "tink/aead.h" #include "tink/aead/aead_config.h" #include "tink/aead/aead_key_templates.h" #include "tink/aead/kms_envelope_aead.h" #include "tink/integration/gcpkms/gcp_kms_client.h" #include "proto/tink.pb.h" ABSL_FLAG(std::string, mode, "", "Mode of operation {encrypt|decrypt}"); ABSL_FLAG(std::string, kek_uri, "", "URI of the KMS Key Encryption Key to use"); ABSL_FLAG(std::string, input_filename, "", "Input file name"); ABSL_FLAG(std::string, output_filename, "", "Output file name"); ABSL_FLAG(std::string, credentials, "", "Optional Google Cloud KMS credentials file path; if not specified, " "use the default credentials"); ABSL_FLAG(std::string, associated_data, "", "Optional associated data"); namespace { using ::crypto::tink::Aead; using ::crypto::tink::AeadKeyTemplates; using ::crypto::tink::integration::gcpkms::GcpKmsClient; using ::google::crypto::tink::KeyTemplate; constexpr absl::string_view kEncrypt = "encrypt"; constexpr absl::string_view kDecrypt = "decrypt"; void ValidateParams() { // ... } absl::StatusOr<std::string> ReadFile(absl::string_view filename) { // ... } absl::Status WriteToFile(absl::string_view data_to_write, absl::string_view filename) { // ... } } // namespace namespace tink_cc_gcpkms_examples { void KmsEnvelopAeadCli(absl::string_view mode, absl::string_view kek_uri, absl::string_view input_filename, absl::string_view output_filename, absl::string_view credentials, absl::string_view associated_data) { CHECK_OK(crypto::tink::AeadConfig::Register()); // Obtain a remote Aead that can use the KEK. absl::StatusOr<std::unique_ptr<GcpKmsClient>> gcp_kms_client = GcpKmsClient::New(kek_uri, credentials); CHECK_OK(gcp_kms_client.status()); absl::StatusOr<std::unique_ptr<Aead>> remote_aead = (*gcp_kms_client)->GetAead(kek_uri); CHECK_OK(remote_aead.status()); // Define the DEK template. KeyTemplate dek_key_template = AeadKeyTemplates::Aes256Gcm(); // Create a KmsEnvelopeAead instance. absl::StatusOr<std::unique_ptr<Aead>> aead = crypto::tink::KmsEnvelopeAead::New(dek_key_template, *std::move(remote_aead)); CHECK_OK(aead.status()); absl::StatusOr<std::string> input_file_content = ReadFile(input_filename); CHECK_OK(input_file_content.status()); if (mode == kEncrypt) { // Generate the ciphertext. absl::StatusOr<std::string> encrypt_result = (*aead)->Encrypt(*input_file_content, associated_data); CHECK_OK(encrypt_result.status()); CHECK_OK(WriteToFile(encrypt_result.value(), output_filename)); } else { // mode == kDecrypt. // Recover the plaintext. absl::StatusOr<std::string> decrypt_result = (*aead)->Decrypt(*input_file_content, associated_data); CHECK_OK(decrypt_result.status()); CHECK_OK(WriteToFile(decrypt_result.value(), output_filename)); } } } // namespace tink_cc_gcpkms_examples int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); ValidateParams(); std::string mode = absl::GetFlag(FLAGS_mode); std::string kek_uri = absl::GetFlag(FLAGS_kek_uri); std::string input_filename = absl::GetFlag(FLAGS_input_filename); std::string output_filename = absl::GetFlag(FLAGS_output_filename); std::string credentials = absl::GetFlag(FLAGS_credentials); std::string associated_data = absl::GetFlag(FLAGS_associated_data); LOG(INFO) << "Using kek-uri " << kek_uri << " with " << (credentials.empty() ? "default credentials" : absl::StrCat("credentials file ", credentials)) << " to envelope " << mode << " file " << input_filename << " with associated data '" << associated_data << "'." << '\n'; LOG(INFO) << "The resulting output will be written to " << output_filename << '\n'; tink_cc_gcpkms_examples::KmsEnvelopAeadCli(mode, kek_uri, input_filename, output_filename, credentials, associated_data); return 0; }
Go
import ( "fmt" "log" "github.com/tink-crypto/tink-go/v2/aead" "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_kmsEnvelopeAEAD() { // 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) } // Get the KMS envelope AEAD primitive. primitive := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kekAEAD) // Use the primitive. plaintext := []byte("message") associatedData := []byte("example KMS envelope AEAD 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 }
Java
この例では、Google Cloud KMS 拡張機能
tink-java-gcpkms が必要です。
package envelopeaead; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.crypto.tink.Aead; import com.google.crypto.tink.KmsClient; import com.google.crypto.tink.aead.AeadConfig; import com.google.crypto.tink.aead.KmsEnvelopeAead; import com.google.crypto.tink.aead.PredefinedAeadParameters; import com.google.crypto.tink.integration.gcpkms.GcpKmsClient; import java.io.File; import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.security.GeneralSecurityException; /** * A command-line utility for encrypting small files with envelope encryption. * * <p>It requires the following arguments: * * <ul> * <li>mode: Can be "encrypt" or "decrypt" to encrypt/decrypt the input to the output. * <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: Read the input from this file. * <li>output-file: Write the result to this file. * <li>[optional] associated-data: Associated data used for the encryption or decryption. */ public final class EnvelopeAeadExample { private static final String MODE_ENCRYPT = "encrypt"; private static final String MODE_DECRYPT = "decrypt"; public static void main(String[] args) throws Exception { if (args.length != 5 && args.length != 6) { System.err.printf("Expected 5 or 6 parameters, got %d\n", args.length); System.err.println( "Usage: java EnvelopeAeadExample encrypt/decrypt kek-uri gcp-credential-file" + " input-file output-file [associated-data]"); System.exit(1); } String mode = args[0]; String kekUri = args[1]; String gcpCredentialFilename = args[2]; byte[] input = Files.readAllBytes(Paths.get(args[3])); File outputFile = new File(args[4]); byte[] associatedData = new byte[0]; if (args.length == 6) { System.out.println("Associated data!"); associatedData = args[5].getBytes(UTF_8); } // Initialise Tink: register all AEAD key types with the Tink runtime AeadConfig.register(); // Read the GCP credentials and create a remote AEAD object. Aead remoteAead = null; try { KmsClient kmsClient = new GcpKmsClient().withCredentials(gcpCredentialFilename); remoteAead = kmsClient.getAead(kekUri); } catch (GeneralSecurityException ex) { System.err.println("Error initializing GCP client: " + ex); System.exit(1); } // Create envelope AEAD primitive using AES256 GCM for encrypting the data Aead aead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES256_GCM, remoteAead); // Use the primitive to encrypt/decrypt files. if (MODE_ENCRYPT.equals(mode)) { byte[] ciphertext = aead.encrypt(input, associatedData); try (FileOutputStream stream = new FileOutputStream(outputFile)) { stream.write(ciphertext); } } else if (MODE_DECRYPT.equals(mode)) { byte[] plaintext = aead.decrypt(input, associatedData); try (FileOutputStream stream = new FileOutputStream(outputFile)) { stream.write(plaintext); } } else { System.err.println("The first argument must be either encrypt or decrypt, got: " + mode); System.exit(1); } System.exit(0); } private EnvelopeAeadExample() {} }
Python
"""A command-line utility for encrypting small files using envelope encryption with GCP.""" 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, ['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('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 used for the encryption.' ) 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) 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 with open(FLAGS.input_path, 'rb') as input_file: input_data = input_file.read() if FLAGS.mode == 'decrypt': output_data = env_aead.decrypt(input_data, associated_data) elif FLAGS.mode == 'encrypt': output_data = env_aead.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', 'kek_uri', 'gcp_credential_path', 'input_path', 'output_path'] ) app.run(main)
次のステップ
クライアントサイド暗号化を使用して、Cloud SQL データベースに追加する前にデータを暗号化できます。
Tink と Cloud KMS を使用して、Google Cloud Storage にアップロードする前にデータを暗号化する方法について学習します。