تقدّم Tink حلولاً لتجنُّب إدارة المفاتيح بشكل غير سليم، ما يمثّل مصدرًا رئيسيًا للمخاطر.
إنشاء المفاتيح وتدويرها
بعد اختيار عنصر أساسي ونوع مفتاح لحالة الاستخدام (في القسم السابق أريد...)، يمكنك إدارة مفاتيحك باستخدام نظام إدارة المفاتيح (KMS) الخارجي الذي اخترته:
أنشئ مفتاح تشفير المفتاح (KEK) في "نظام إدارة المفاتيح" لحماية مفاتيحك.
استرِد معرّف موارد منتظم (URI) وبيانات اعتماد المفتاح من "نظام إدارة المفاتيح" لتمريرها إلى Tink.
استخدِم واجهات برمجة التطبيقات في Tink أو Tinkey لـ إنشاء مجموعة مفاتيح مشفّرة. بعد تشفير مفاتيحك، يمكنك تخزينها في أي مكان تريده.
دوِّر مفاتيحك لتجنُّب إعادة استخدامها بشكل كبير و للاسترداد من اختراق المفاتيح.
إذا كنت بحاجة إلى تصدير المفاتيح، يُرجى الاطّلاع على مقالة تصدير مادة المفتاح آليًا للحصول على تفاصيل حول كيفية إجراء ذلك بأمان.
الخطوة 1: إنشاء مفتاح تشفير المفتاح في "نظام إدارة المفاتيح" الخارجي
أنشئ مفتاح تشفير المفتاح (KEK) في "نظام إدارة المفاتيح" الخارجي. يحمي مفتاح تشفير المفتاح مفاتيحك من خلال تشفيرها، ما يضيف طبقة أمان إضافية.
يُرجى الرجوع إلى المستندات الخاصة بـ "نظام إدارة المفاتيح" لإنشاء مفتاح تشفير المفتاح:
- Google Cloud KMS
- Amazon KMS
- HashiCorp Vault (متاح حاليًا بلغة Go فقط)
الخطوة 2: الحصول على معرّف موارد منتظم (URI) وبيانات اعتماد المفتاح
يمكنك استرداد كل من معرّف الموارد المنتظم (URI) وبيانات اعتماد المفتاح من "نظام إدارة المفاتيح".
الحصول على معرّف الموارد المنتظم (URI) للمفتاح
تتطلّب Tink معرّف موارد منتظم (URI) للعمل مع مفاتيح "نظام إدارة المفاتيح".
لإنشاء معرّف الموارد المنتظم (URI) هذا، استخدِم المعرّف الفريد الذي يخصّصه "نظام إدارة المفاتيح" للمفتاح عند إنشائه. أضِف البادئة المناسبة الخاصة بـ "نظام إدارة المفاتيح" واتّبِع تنسيق معرّفات الموارد المنتظمة (URI) للمفاتيح المتوافقة كما هو موضّح في هذا الجدول:
| KMS | بادئة معرّف "نظام إدارة المفاتيح" | تنسيق معرّف الموارد المنتظم (URI) للمفتاح |
|---|---|---|
| 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] |
الحصول على بيانات اعتماد المفتاح
أعِدّ بيانات الاعتماد اللازمة حتى تتمكّن Tink من المصادقة على "نظام إدارة المفاتيح" الخارجي.
يختلف شكل بيانات الاعتماد حسب "نظام إدارة المفاتيح":
- Google Cloud KMS: تتطلّب Tink بيانات اعتماد حسابات الخدمة. وهي ملف JSON يمكن إنشاؤه وتنزيله من Google Cloud Console.
- AWS KMS: تتطلّب Tink ملف بيانات اعتماد يحتوي
على
- رقم تعريف مفتاح الوصول access key ID في السمة
accessKeyproperty و - مفتاح الوصول السري في السمة
secretKey.
- رقم تعريف مفتاح الوصول access key ID في السمة
- HashiCorp Vault: تتطلّب Tink رموز خدمة يمكن إنشاؤها باستخدام الأمر vault token create command.
إذا لم تقدّم بيانات الاعتماد، تحاول Tink تحميل بيانات الاعتماد التلقائية. لمزيد من المعلومات، يُرجى الرجوع إلى المستندات الخاصة بـ "نظام إدارة المفاتيح":
الخطوة 3: إنشاء مجموعة مفاتيح مشفّرة وتخزينها
استخدِم واجهات برمجة التطبيقات في Tink (لخدمة Google Cloud KMS أو AWS KMS أو HashiCorp Vault) أو Tinkey لإنشاء مجموعة مفاتيح وتشفيرها باستخدام "نظام إدارة المفاتيح" الخارجي وتخزينها في مكان ما.
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
جافا
في هذا المثال، تحتاج إلى إضافة 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 }
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)
الخطوة 4: تدوير المفاتيح
لضمان أمان نظامك، عليك تدوير المفاتيح.
- فعِّل ميزة تدوير المفاتيح تلقائيًا في "نظام إدارة المفاتيح".
حدِّد وتيرة مناسبة لتدوير المفاتيح. يعتمد ذلك على مدى حساسية بياناتك وعدد الرسائل التي تحتاج إلى تشفيرها وما إذا كان عليك تنسيق عملية التدوير مع شركاء خارجيين.
- بالنسبة إلى التشفير المتماثل ، استخدِم مفاتيح تتراوح مدتها بين 30 و90 يومًا.
- بالنسبة إلى التشفير غير المتماثل ، يمكن أن تكون وتيرة التدوير أقل، ولكن فقط إذا كان بإمكانك إبطال المفاتيح بأمان.
مزيد من المعلومات حول تدوير المفاتيح في المستندات الخاصة بـ "نظام إدارة المفاتيح":