เราขอแนะนำให้ใช้ Primitive Hybrid Encryption ที่มีคีย์ ประเภท DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, AES_256_GCM สำหรับกรณีการใช้งานการเข้ารหัสคีย์สาธารณะส่วนใหญ่
การเข้ารหัสคีย์สาธารณะเกี่ยวข้องกับการปกป้องข้อมูลด้วยคีย์ 2 คีย์ ได้แก่ คีย์สาธารณะและคีย์ส่วนตัว คีย์สาธารณะใช้สำหรับการเข้ารหัส และคีย์ส่วนตัวใช้ สำหรับการถอดรหัส นี่เป็นตัวเลือกที่ดีหากผู้ส่งจัดเก็บข้อมูลลับไม่ได้และ ต้องเข้ารหัสข้อมูลด้วยคีย์สาธารณะ
ตัวอย่างต่อไปนี้จะช่วยให้คุณเริ่มต้นใช้งาน Primitive การเข้ารหัสแบบผสมได้
C++
// A command-line utility for testing Tink Hybrid Encryption. #include <iostream> #include <memory> #include <ostream> #include <string> #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "tink/config/global_registry.h" #include "util/util.h" #ifndef TINK_EXAMPLES_EXCLUDE_HPKE #include "tink/hybrid/hpke_config.h" #endif #include "tink/hybrid/hybrid_config.h" #include "tink/hybrid_decrypt.h" #include "tink/hybrid_encrypt.h" #include "tink/keyset_handle.h" ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format"); ABSL_FLAG(std::string, mode, "", "Mode of operation {encrypt|decrypt}"); ABSL_FLAG(std::string, input_filename, "", "Input file name"); ABSL_FLAG(std::string, output_filename, "", "Output file name"); ABSL_FLAG(std::string, context_info, "", "Context info for Hybrid Encryption/Decryption"); namespace { using ::crypto::tink::HybridDecrypt; using ::crypto::tink::HybridEncrypt; using ::crypto::tink::KeysetHandle; constexpr absl::string_view kEncrypt = "encrypt"; constexpr absl::string_view kDecrypt = "decrypt"; void ValidateParams() { // ... } } // namespace namespace tink_cc_examples { absl::Status HybridCli(absl::string_view mode, const std::string& keyset_filename, const std::string& input_filename, const std::string& output_filename, absl::string_view context_info) { absl::Status result = crypto::tink::HybridConfig::Register(); if (!result.ok()) return result; #ifndef TINK_EXAMPLES_EXCLUDE_HPKE // HPKE isn't supported when using OpenSSL as a backend. result = crypto::tink::RegisterHpke(); if (!result.ok()) return result; #endif // Read the keyset from file. absl::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle = ReadJsonCleartextKeyset(keyset_filename); if (!keyset_handle.ok()) return keyset_handle.status(); // Read the input. absl::StatusOr<std::string> input_file_content = ReadFile(input_filename); if (!input_file_content.ok()) return input_file_content.status(); // Compute the output. std::string output; if (mode == kEncrypt) { // Get the hybrid encryption primitive. absl::StatusOr<std::unique_ptr<HybridEncrypt>> hybrid_encrypt_primitive = (*keyset_handle) ->GetPrimitive<crypto::tink::HybridEncrypt>( crypto::tink::ConfigGlobalRegistry()); if (!hybrid_encrypt_primitive.ok()) { return hybrid_encrypt_primitive.status(); } // Generate the ciphertext. absl::StatusOr<std::string> encrypt_result = (*hybrid_encrypt_primitive)->Encrypt(*input_file_content, context_info); if (!encrypt_result.ok()) return encrypt_result.status(); output = encrypt_result.value(); } else { // operation == kDecrypt. // Get the hybrid decryption primitive. absl::StatusOr<std::unique_ptr<HybridDecrypt>> hybrid_decrypt_primitive = (*keyset_handle) ->GetPrimitive<crypto::tink::HybridDecrypt>( crypto::tink::ConfigGlobalRegistry()); if (!hybrid_decrypt_primitive.ok()) { return hybrid_decrypt_primitive.status(); } // Recover the plaintext. absl::StatusOr<std::string> decrypt_result = (*hybrid_decrypt_primitive)->Decrypt(*input_file_content, context_info); if (!decrypt_result.ok()) return decrypt_result.status(); output = decrypt_result.value(); } // Write the output to the output file. return WriteToFile(output, output_filename); } } // namespace tink_cc_examples int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); ValidateParams(); std::string mode = absl::GetFlag(FLAGS_mode); std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename); std::string input_filename = absl::GetFlag(FLAGS_input_filename); std::string output_filename = absl::GetFlag(FLAGS_output_filename); std::string context_info = absl::GetFlag(FLAGS_context_info); std::clog << "Using keyset from file " << keyset_filename << " to hybrid " << mode << " file " << input_filename << " with context info '" << context_info << "'." << '\n'; std::clog << "The resulting output will be written to " << output_filename << '\n'; ABSL_CHECK_OK(tink_cc_examples::HybridCli( mode, keyset_filename, input_filename, output_filename, context_info)); return 0; }
Go
import ( "bytes" "fmt" "log" "github.com/tink-crypto/tink-go/v2/hybrid" "github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset" "github.com/tink-crypto/tink-go/v2/keyset" ) func Example() { // A private keyset created with // "tinkey create-keyset --key-template=DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM --out private_keyset.cfg". // Note that this keyset has the secret key information in cleartext. privateJSONKeyset := `{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PRIVATE", "typeUrl": "type.googleapis.com/google.crypto.tink.HpkePrivateKey", "value": "EioSBggBEAEYAhogVWQpmQoz74jcAp5WOD36KiBQ71MVCpn2iWfOzWLtKV4aINfn8qlMbyijNJcCzrafjsgJ493ZZGN256KTfKw0WN+p" }, "keyId": 958452012, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 958452012 }` // The corresponding public keyset created with // "tinkey create-public-keyset --in private_keyset.cfg". publicJSONKeyset := `{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PUBLIC", "typeUrl": "type.googleapis.com/google.crypto.tink.HpkePublicKey", "value": "EgYIARABGAIaIFVkKZkKM++I3AKeVjg9+iogUO9TFQqZ9olnzs1i7Sle" }, "keyId": 958452012, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 958452012 }` // Create a keyset handle from the keyset containing the public key. Because the // public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets]. publicKeysetHandle, err := keyset.ReadWithNoSecrets( keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset))) if err != nil { log.Fatal(err) } // Retrieve the HybridEncrypt primitive from publicKeysetHandle. encPrimitive, err := hybrid.NewHybridEncrypt(publicKeysetHandle) if err != nil { log.Fatal(err) } plaintext := []byte("message") encryptionContext := []byte("encryption context") ciphertext, err := encPrimitive.Encrypt(plaintext, encryptionContext) if err != nil { log.Fatal(err) } // Create a keyset handle from the cleartext private keyset in the previous // step. The keyset handle provides abstract access to the underlying keyset to // limit the access of the raw key material. WARNING: In practice, // it is unlikely you will want to use a insecurecleartextkeyset, as it implies // that your key material is passed in cleartext, which is a security risk. // Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault. // See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets. privateKeysetHandle, err := insecurecleartextkeyset.Read( keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset))) if err != nil { log.Fatal(err) } // Retrieve the HybridDecrypt primitive from privateKeysetHandle. decPrimitive, err := hybrid.NewHybridDecrypt(privateKeysetHandle) if err != nil { log.Fatal(err) } decrypted, err := decPrimitive.Decrypt(ciphertext, encryptionContext) if err != nil { log.Fatal(err) } fmt.Println(string(decrypted)) // Output: message }
Java
package hybrid; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.crypto.tink.HybridDecrypt; import com.google.crypto.tink.HybridEncrypt; import com.google.crypto.tink.InsecureSecretKeyAccess; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.RegistryConfiguration; import com.google.crypto.tink.TinkJsonProtoKeysetFormat; import com.google.crypto.tink.hybrid.HybridConfig; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * A command-line utility for hybrid encryption. * * <p>It loads cleartext keys from disk - this is not recommended! * * <p>It requires the following arguments: * * <ul> * <li>mode: either 'encrypt' or 'decrypt'. * <li>key-file: Read the key material from this file. * <li>input-file: Read the input from this file. * <li>output-file: Write the result to this file. * <li>[optional] contex-info: Bind the encryption to this context info. */ public final class HybridExample { public static void main(String[] args) throws Exception { if (args.length != 4 && args.length != 5) { System.err.printf("Expected 4 or 5 parameters, got %d\n", args.length); System.err.println( "Usage: java HybridExample encrypt/decrypt key-file input-file output-file context-info"); System.exit(1); } String mode = args[0]; if (!mode.equals("encrypt") && !mode.equals("decrypt")) { System.err.println("Incorrect mode. Please select encrypt or decrypt."); System.exit(1); } Path keyFile = Paths.get(args[1]); Path inputFile = Paths.get(args[2]); byte[] input = Files.readAllBytes(inputFile); Path outputFile = Paths.get(args[3]); byte[] contextInfo = new byte[0]; if (args.length == 5) { contextInfo = args[4].getBytes(UTF_8); } // Register all hybrid encryption key types with the Tink runtime. HybridConfig.register(); // Read the keyset into a KeysetHandle. KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset( new String(Files.readAllBytes(keyFile), UTF_8), InsecureSecretKeyAccess.get()); if (mode.equals("encrypt")) { // Get the primitive. HybridEncrypt encryptor = handle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); // Use the primitive to encrypt data. byte[] ciphertext = encryptor.encrypt(input, contextInfo); Files.write(outputFile, ciphertext); } else { HybridDecrypt decryptor = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); // Use the primitive to decrypt data. byte[] plaintext = decryptor.decrypt(input, contextInfo); Files.write(outputFile, plaintext); } } private HybridExample() {} }
Obj-C
Python
import tink from tink import hybrid from tink import secret_key_access def example(): """Encrypt and decrypt using hybrid encryption.""" # Register the hybrid encryption key managers. This is needed to create # HybridEncrypt and HybridDecrypt primitives later. hybrid.register() # A private keyset created with # tinkey create-keyset \ # --key-template=DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM \ # --out private_keyset.cfg # Note that this keyset has the secret key information in cleartext. private_keyset = r"""{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PRIVATE", "typeUrl": "type.googleapis.com/google.crypto.tink.HpkePrivateKey", "value": "EioSBggBEAEYAhogVWQpmQoz74jcAp5WOD36KiBQ71MVCpn2iWfOzWLtKV4aINfn8qlMbyijNJcCzrafjsgJ493ZZGN256KTfKw0WN+p" }, "keyId": 958452012, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 958452012 }""" # The corresponding public keyset created with # "tinkey create-public-keyset --in private_keyset.cfg" public_keyset = r"""{ "key": [{ "keyData": { "keyMaterialType": "ASYMMETRIC_PUBLIC", "typeUrl": "type.googleapis.com/google.crypto.tink.HpkePublicKey", "value": "EgYIARABGAIaIFVkKZkKM++I3AKeVjg9+iogUO9TFQqZ9olnzs1i7Sle" }, "keyId": 958452012, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 958452012 }""" # Create a keyset handle from the keyset containing the public key. Because # this keyset does not contain any secrets, we can use # `parse_without_secret`. public_keyset_handle = tink.json_proto_keyset_format.parse_without_secret( public_keyset ) # Retrieve the HybridEncrypt primitive from the keyset handle. enc_primitive = public_keyset_handle.primitive(hybrid.HybridEncrypt) # Use enc_primitive to encrypt a message. In this case the primary key of the # keyset will be used (which is also the only key in this example). ciphertext = enc_primitive.encrypt(b'message', b'context_info') # Create a keyset handle from the private keyset. The keyset handle provides # abstract access to the underlying keyset to limit the exposure of accessing # the raw key material. WARNING: In practice, it is unlikely you will want to # use a tink.json_proto_keyset_format.parse, as it implies that your key # material is passed in cleartext which is a security risk. private_keyset_handle = tink.json_proto_keyset_format.parse( private_keyset, secret_key_access.TOKEN ) # Retrieve the HybridDecrypt primitive from the private keyset handle. dec_primitive = private_keyset_handle.primitive(hybrid.HybridDecrypt) # Use dec_primitive to decrypt the message. Decrypt finds the correct key in # the keyset and decrypts the ciphertext. If no key is found or decryption # fails, it raises an error. decrypted = dec_primitive.decrypt(ciphertext, b'context_info')
การเข้ารหัสแบบผสม
Primitive การเข้ารหัสแบบผสมผสานรวมประสิทธิภาพของการเข้ารหัสแบบสมมาตร เข้ากับความสะดวกของวิทยาการเข้ารหัสคีย์สาธารณะ (แบบอสมมาตร) ทุกคนสามารถเข้ารหัส ข้อมูลได้โดยใช้คีย์สาธารณะ แต่มีเพียงผู้ใช้ที่มีคีย์ส่วนตัวเท่านั้นที่ถอดรหัส ข้อมูลได้
สำหรับการเข้ารหัสแบบผสม ผู้ส่งจะสร้างคีย์สมมาตรใหม่เพื่อเข้ารหัส ข้อความธรรมดาของแต่ละข้อความเพื่อสร้างข้อความที่เข้ารหัส ระบบจะแคปซูลคีย์สมมาตรนั้นด้วยคีย์สาธารณะของผู้รับ สำหรับการถอดรหัสแบบผสม ผู้รับจะแยกแคปซูลคีย์สมมาตร แล้วใช้คีย์ดังกล่าวเพื่อถอดรหัสข้อความที่เข้ารหัสเพื่อกู้คืนข้อความธรรมดาต้นฉบับ ดูรายละเอียดเกี่ยวกับวิธีจัดเก็บหรือ ส่งข้อความที่เข้ารหัสพร้อมกับการห่อหุ้มคีย์ได้ที่รูปแบบสายของ Tink Hybrid Encryption
การเข้ารหัสแบบผสมมีคุณสมบัติดังนี้
- Secrecy: ไม่มีใครสามารถรับข้อมูลใดๆ เกี่ยวกับข้อความธรรมดาที่เข้ารหัส (ยกเว้นความยาว) ได้ เว้นแต่จะมีสิทธิ์เข้าถึงคีย์ส่วนตัว
- อสมมาตร: คุณเข้ารหัสข้อความที่เข้ารหัสได้ด้วยคีย์สาธารณะ แต่ต้องใช้คีย์ส่วนตัวในการถอดรหัส
- การสุ่ม: การเข้ารหัสจะสุ่ม ข้อความ 2 รายการที่มีข้อความธรรมดาเหมือนกันจะให้ข้อความที่เข้ารหัสเหมือนกันไม่ได้ ซึ่งจะป้องกันไม่ให้ผู้โจมตีทราบว่าข้อความที่เข้ารหัสข้อความใดสอดคล้องกับข้อความธรรมดาที่กำหนด
การเข้ารหัสแบบผสมจะแสดงใน Tink เป็นคู่ของ Primitive ดังนี้
- HybridEncrypt สำหรับการเข้ารหัส
- HybridDecrypt สำหรับการถอดรหัส
พารามิเตอร์ข้อมูลบริบท
นอกเหนือจากข้อความธรรมดาแล้ว การเข้ารหัสแบบไฮบริดยังยอมรับพารามิเตอร์เพิ่มเติม
context_info ซึ่งมักจะเป็นข้อมูลสาธารณะที่ได้จากบริบท แต่
ควรเชื่อมโยงกับข้อความเข้ารหัสที่ได้ ซึ่งหมายความว่าข้อความที่เข้ารหัส
ช่วยให้คุณยืนยันความสมบูรณ์ของข้อมูลบริบทได้ แต่ไม่มี
การรับประกันความลับหรือความถูกต้อง ข้อมูลบริบทจริงอาจว่างเปล่า
หรือเป็นค่า Null แต่เพื่อให้มั่นใจว่าการถอดรหัสข้อความที่เข้ารหัสที่ได้นั้นถูกต้อง คุณต้องระบุค่าข้อมูลบริบทเดียวกันสำหรับการถอดรหัส
การใช้งานการเข้ารหัสแบบผสมอย่างเป็นรูปธรรมสามารถเชื่อมโยงข้อมูลบริบทกับ ข้อความที่เข้ารหัสได้หลายวิธี เช่น
- ใช้
context_infoเป็นข้อมูลที่เชื่อมโยงสำหรับการเข้ารหัสแบบสมมาตร AEAD (ดู RFC 5116) - ใช้
context_infoเป็นอินพุต "CtxInfo" สำหรับ HKDF (หากการติดตั้งใช้งานใช้ HKDF เป็นฟังก์ชันการได้คีย์ โปรดดู RFC 5869)
เลือกประเภทคีย์
เราขอแนะนำให้ใช้คีย์ประเภท DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM
สำหรับ Use Case ส่วนใหญ่ คีย์ประเภทนี้ใช้มาตรฐานการเข้ารหัสคีย์สาธารณะแบบผสม (HPKE) ตามที่ระบุไว้ใน RFC
9180 HPKE ประกอบด้วย
กลไกการห่อหุ้มคีย์ (KEM), ฟังก์ชันการได้คีย์ (KDF) และ
อัลกอริทึมการเข้ารหัสที่ผ่านการรับรองพร้อมข้อมูลที่เชื่อมโยง (AEAD)
DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM โดยเฉพาะอย่างยิ่ง
- KEM: Diffie–Hellman over Curve25519 ที่มี HKDF-SHA-256 เพื่อสร้าง ความลับที่ใช้ร่วมกัน
- KDF: HKDF-SHA-256 เพื่อสร้างบริบทของผู้ส่งและผู้รับ
- AEAD: AES-256-GCM ที่มี Nonce ขนาด 12 ไบต์ซึ่งสร้างขึ้นตามมาตรฐาน HPKE
คีย์ HPKE ประเภทอื่นๆ ที่รองรับรวมถึงแต่ไม่จำกัดเพียงรายการต่อไปนี้
DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_128_GCMDHKEM_X25519_HKDF_SHA256_HKDF_SHA256_CHACHA20_POLY1305DHKEM_P256_HKDF_SHA256_HKDF_SHA256_AES_128_GCMDHKEM_P521_HKDF_SHA512_HKDF_SHA512_AES_256_GCM
ดูรายละเอียดเพิ่มเติมเกี่ยวกับตัวเลือกอัลกอริทึมสำหรับ KEM, KDF และ AEAD ได้ที่ RFC 9180
แม้ว่าเราจะไม่แนะนำให้ใช้แล้ว แต่ Tink ยังรองรับ ECIES บางรูปแบบตามที่อธิบายไว้ในมาตรฐาน ISO 18033-2 ของ Victor Shoup คีย์ ECIES บางประเภทที่รองรับมีดังนี้
ECIES_P256_HKDF_HMAC_SHA256_AES128_GCMECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCMECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256
พร็อพเพอร์ตี้ขั้นต่ำ
- ข้อความธรรมดาและข้อมูลบริบทมีความยาวเท่าใดก็ได้ (ภายในช่วง 0..232 ไบต์)
- ปลอดภัยจากการโจมตีด้วยข้อความที่เข้ารหัสที่เลือกแบบปรับเปลี่ยนได้
- ความปลอดภัย 128 บิตสำหรับรูปแบบที่อิงตามเส้นโค้งวงรี