Tink cung cấp các giải pháp để tránh việc quản lý khoá không đúng cách, đây là nguồn gốc chính của rủi ro.
Tạo và xoay khoá
Sau khi chọn một kiểu khoá và kiểu nguyên thuỷ cho trường hợp sử dụng của bạn (trong phần Tôi muốn... ở trên), hãy quản lý khoá bằng Hệ thống quản lý khoá (KMS) bên ngoài mà bạn đã chọn:
Tạo khoá mã hoá khoá (KEK) trong KMS để bảo vệ khoá của bạn.
Truy xuất URI khoá và thông tin xác thực khoá từ KMS để truyền đến Tink.
Sử dụng API của Tink hoặc Tinkey để tạo một khoá được mã hoá. Sau khi khoá được mã hoá, bạn có thể lưu trữ khoá ở bất cứ nơi nào bạn muốn.
Xoay vòng khoá để tránh sử dụng lại khoá quá nhiều lần và để khôi phục sau khi khoá bị xâm phạm.
Nếu bạn cần xuất khoá, hãy xem phần Xuất khoá theo chương trình để biết thông tin chi tiết về cách thực hiện một cách an toàn.
Bước 1: Tạo KEK trong KMS bên ngoài
Tạo khoá mã hoá khoá (KEK) trong KMS bên ngoài. KEK bảo vệ các khoá của bạn bằng cách mã hoá chúng, giúp tăng cường thêm một lớp bảo mật.
Tham khảo tài liệu dành riêng cho KMS để tạo KEK:
- Google Cloud KMS
- Amazon KMS
- HashiCorp Vault (hiện chỉ có bằng ngôn ngữ Go)
Bước 2: Lấy URI khoá và thông tin đăng nhập
Bạn có thể truy xuất cả URI khoá và thông tin xác thực khoá từ KMS.
Lấy URI khoá
Tink yêu cầu Giá trị nhận dạng tài nguyên đồng nhất (URI) để hoạt động với các khoá KMS.
Để tạo URI này, hãy sử dụng giá trị nhận dạng duy nhất mà KMS chỉ định cho khoá khi khoá được tạo. Thêm tiền tố thích hợp dành riêng cho KMS và tuân theo định dạng của các URI khoá được hỗ trợ như mô tả trong bảng này:
| KMS | Tiền tố giá trị nhận dạng KMS | Định dạng URI khoá |
|---|---|---|
| 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] |
Lấy thông tin đăng nhập chính
Chuẩn bị thông tin đăng nhập cần thiết để Tink có thể xác thực với KMS bên ngoài.
Hình thức chính xác của thông tin đăng nhập là dành riêng cho KMS:
- Google Cloud KMS – Tink yêu cầu thông tin đăng nhập tài khoản dịch vụ. Đây là một tệp JSON có thể được tạo và tải xuống từ Google Cloud Console.
- AWS KMS – Tink yêu cầu một tệp thông tin đăng nhập chứa
- mã khoá truy cập trong thuộc tính
accessKeyvà - khoá truy cập bí mật trong thuộc tính
secretKey.
- mã khoá truy cập trong thuộc tính
- HashiCorp Vault – Tink yêu cầu mã thông báo dịch vụ có thể được tạo bằng lệnh tạo mã thông báo vault.
Nếu bạn không cung cấp thông tin đăng nhập, Tink sẽ cố gắng tải thông tin đăng nhập mặc định. Để biết thêm thông tin, hãy tham khảo tài liệu dành riêng cho KMS:
Bước 3: Tạo và lưu trữ một bộ khoá được mã hoá
Sử dụng các API của Tink (cho Google Cloud KMS, AWS KMS hoặc HashiCorp Vault) hoặc Tinkey để tạo một keyset, mã hoá keyset đó bằng KMS bên ngoài và lưu trữ ở một nơi nào đó.
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
Đối với ví dụ này, bạn cần có tiện ích 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() {} }
Go
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)
Bước 4: Chuyển đổi khoá
Để đảm bảo tính bảo mật của hệ thống, bạn phải xoay khoá.
- Bật tính năng xoay vòng khoá tự động trong KMS.
Xác định tần suất phù hợp để xoay khoá. Điều này phụ thuộc vào mức độ nhạy cảm của dữ liệu, số lượng thư bạn cần mã hoá và việc bạn có phải phối hợp xoay vòng khoá với các đối tác bên ngoài hay không.
- Đối với hoạt động mã hoá đối xứng, hãy sử dụng khoá từ 30 đến 90 ngày.
- Đối với hoạt động mã hoá không đối xứng, tần suất xoay có thể thấp hơn, nhưng chỉ khi bạn có thể thu hồi khoá một cách an toàn.
Tìm hiểu thêm về tính năng xoay vòng khoá trong tài liệu dành riêng cho KMS: