אני רוצה להחליף נתונים

מומלץ להשתמש בפרימיטיב של הצפנה היברידית עם המפתח מסוג DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, AES_256_GCM ברוב התרחישים לדוגמה של הצפנת מפתחות ציבוריים.

בהצפנה של מפתחות ציבוריים, כדי להגן על הנתונים באמצעות שני מפתחות: אחד ציבורי והשני פרטי. המפתח הציבורי משמש להצפנה והמפתח הפרטי משמש לפענוח. כדאי להשתמש באפשרות הזו אם השולח לא יכול לשמור סודות וצריך להצפין את הנתונים באמצעות מפתח ציבורי.

הדוגמאות הבאות יעזרו לך להתחיל להשתמש בפרימיטיב של הצפנה היברידית:

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/check.h"
#include "absl/strings/string_view.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"
#include "tink/util/status.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;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

constexpr absl::string_view kEncrypt = "encrypt";
constexpr absl::string_view kDecrypt = "decrypt";

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

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) {
  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.
  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();

  // Read the input.
  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.
    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.
    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.
    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.
    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 << "'." << std::endl;
  std::clog << "The resulting output will be written to " << output_filename
            << std::endl;

  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.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(HybridEncrypt.class);

      // Use the primitive to encrypt data.
      byte[] ciphertext = encryptor.encrypt(input, contextInfo);
      Files.write(outputFile, ciphertext);
    } else {
      HybridDecrypt decryptor = handle.getPrimitive(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')

הצפנה היברידית

פרימיטיב ההצפנה ההיברידית משלב את היעילות של הצפנה סימטרית עם הנוחות של קריפטוגרפיה של מפתח ציבורי (אסימטרית). כל אחד יכול להצפין את הנתונים באמצעות המפתח הציבורי, אבל רק משתמשים שיש להם את המפתח הפרטי יכולים לפענח אותם.

בהצפנה היברידית, השולח יוצר מפתח סימטרי חדש כדי להצפין את הטקסט ללא הצפנה של כל הודעה, כדי להפיק מידע מוצפן. המפתח הסימטרי הזה מוקף במפתח הציבורי של הנמען. בפענוח היברידי, המפתח הסימטרי מפוענח על ידי המקבל, ואז משמש לפענוח המידע מוצפן כדי לשחזר את הטקסט ללא הצפנה המקורי. במאמר בנושא פורמט החוט של Tink Hybrid Encryption ניתן לקרוא איך לאחסן או להעביר את המידע מוצפן (ciphertext) יחד עם אנקפסולציה של המפתח.

להצפנה היברידית יש את המאפיינים הבאים:

  • סודיות: אף אחד לא יכול לקבל מידע על הטקסט ללא הצפנה (מלבד האורך), אלא אם יש לו גישה למפתח הפרטי.
  • אסימטריה: אפשר להצפין את המידע המוצפן באמצעות המפתח הציבורי, אבל לצורך הפענוח נדרש המפתח הפרטי.
  • רנדומיזציה: ההצפנה אקראית. שתי הודעות עם אותו טקסט ללא הצפנה לא יניבו את אותו מידע מוצפן. כך תוקפים לא יכולים לדעת איזה מידע מוצפן (ciphertext) תואם לטקסט ללא הצפנה.

הצפנה היברידית מיוצגת ב-Tink כצמד של פרימיטיבים:

  • HybridEncrypt להצפנה
  • HybridDecrypt לפענוח

פרמטר של פרטי הקשר

בהצפנה ההיברידית אפשר להשתמש בפרמטר נוסף, context_info, שהוא בדרך כלל מידע ציבורי שמשתמע מההקשר, אבל הוא צריך להיות מקושר למידע מוצפן (ciphertext). כלומר, המידע המוצפן מאפשר לאשר את המהימנות של פרטי ההקשר, אבל אין ערובה לסודיות או באותנטיות שלו. פרטי ההקשר עצמם יכולים להיות ריקים או ריקים, אבל כדי להבטיח שהפענוח יוצפן בצורה נכונה של המידע מוצפן (ciphertext), צריך לספק את אותו הערך של פרטי ההקשר, לצורך הפענוח.

הטמעה מוחשית של הצפנה היברידית יכולה לקשר את פרטי ההקשר למידע מוצפן (ciphertext) בדרכים שונות. לדוגמה:

  • משתמשים ב-context_info כקלט נתונים משויך להצפנה סימטרית של AEAD (cf. RFC 5116).
  • משתמשים בפונקציה context_info כקלט "CtxInfo" של HKDF (אם ההטמעה משתמשת ב-HKDF כפונקציית נגזרת מפתח, cf. RFC 5869).

בחירת סוג מפתח

ברוב התרחישים לדוגמה מומלץ להשתמש בסוג המפתח DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM. בסוג המפתח הזה מוטמע תקן הצפנת מפתח ציבורי היברידי (HPKE), כפי שצוין ב-RFC 9180. HPKE מורכב ממנגנון אנקפסולציה של מפתחות (KEM), פונקציית נגזרת מפתחות (KDF) ואלגוריתם הצפנה מאומתת עם נתונים משויכים (AEAD).

DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM משתמש/ת באופן ספציפי ב:

  • KEM: Diffie–Hellman ב-Curve25519 בעזרת HKDF-SHA-256, כדי להסיק את הסוד המשותף.
  • KDF: HKDF-SHA-256 כדי להסיק את ההקשר של השולח והמקבל.
  • AEAD: AES-256-GCM עם צפנים חד-פעמיים של 12 בייטים שנוצרים בהתאם לתקן HPKE.

סוגים נתמכים אחרים של מפתחות HPKE כוללים, בין היתר:

  • DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_128_GCM
  • DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_CHACHA20_POLY1305
  • DHKEM_P256_HKDF_SHA256_HKDF_SHA256_AES_128_GCM
  • DHKEM_P521_HKDF_SHA512_HKDF_SHA512_AES_256_GCM

לפרטים נוספים על אפשרויות האלגוריתם ל-KEM, KDF ו-AEAD, ראו RFC 9180.

למרות ש-Tink כבר לא מומלץ, היא תומכת גם בכמה וריאציות של ECIES כפי שמתואר בתקן ISO 18033-2 של ויקטור שופ. דוגמאות לסוגים נתמכים של מפתחות ECIES:

  • ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM
  • ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCM
  • ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256
  • ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256

תכונות מינימליות

  • פרטי טקסט והקשר יכולים להיות באורך שרירותי (בטווח 0.232 בייטים)
  • אבטחה מפני התקפות של מידע מוצפן (ciphertext) שנבחרו להתאמה
  • אבטחה של 128 ביט לסכמות המבוססות על עקומה אליפטית