Tôi muốn trao đổi dữ liệu

Chúng tôi đề xuất sử dụng nguyên thủy Mã hoá kết hợp với loại khoá DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, AES_256_GCM cho hầu hết các trường hợp sử dụng mã hoá khoá công khai.

Phương thức mã hoá khoá công khai liên quan đến việc bảo vệ dữ liệu bằng hai khoá: một khoá công khai và một khoá riêng tư. Khoá công khai được dùng để mã hoá và khoá riêng tư được dùng để giải mã. Đây là một lựa chọn phù hợp nếu người gửi không thể lưu trữ các khoá bí mật và cần mã hoá dữ liệu bằng khoá công khai.

Các ví dụ sau đây sẽ giúp bạn bắt đầu sử dụng nguyên thuỷ Mã hoá kết hợp:

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

HƯỚNG DẪN

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')

Mã hoá kết hợp

Nguyên tắc Mã hoá kết hợp kết hợp hiệu quả của phương thức mã hoá đối xứng với sự tiện lợi của phương thức mã hoá khoá công khai (bất đối xứng). Mọi người đều có thể mã hoá dữ liệu bằng khoá công khai, nhưng chỉ những người dùng có khoá riêng tư mới có thể giải mã dữ liệu.

Đối với phương thức Mã hoá kết hợp, người gửi sẽ tạo một khoá đối xứng mới để mã hoá văn bản thuần tuý của từng thư nhằm tạo ra một văn bản mã hoá. Khoá đối xứng đó được đóng gói bằng khoá công khai của người nhận. Đối với phương thức Giải mã kết hợp, khoá đối xứng sẽ được người nhận giải đóng gói rồi dùng để giải mã văn bản mã hoá nhằm khôi phục văn bản thuần tuý ban đầu. Hãy xem Định dạng dây mã hoá kết hợp Tink để biết thông tin chi tiết về cách lưu trữ hoặc truyền văn bản mã hoá cùng với tính năng đóng gói khoá.

Phương thức mã hoá kết hợp có các thuộc tính sau:

  • Secrecy: Không ai có thể lấy được bất kỳ thông tin nào về văn bản thuần tuý đã mã hoá (ngoại trừ độ dài), trừ phi họ có quyền truy cập vào khoá riêng tư.
  • Tính bất đối xứng: Bạn có thể mã hoá văn bản mã hoá bằng khoá công khai, nhưng để giải mã, bạn cần có khoá riêng tư.
  • Ngẫu nhiên hoá: Quá trình mã hoá được ngẫu nhiên hoá. Hai thông báo có cùng văn bản thuần tuý sẽ không tạo ra cùng một văn bản mã hoá. Điều này ngăn chặn kẻ tấn công biết được văn bản mã hoá nào tương ứng với một văn bản thuần tuý nhất định.

Mã hoá kết hợp được biểu thị trong Tink dưới dạng một cặp các thành phần cơ bản:

  • HybridEncrypt để mã hoá
  • HybridDecrypt để giải mã

Tham số thông tin theo bối cảnh

Ngoài văn bản thuần tuý, Hybrid Encryption còn chấp nhận một tham số bổ sung, context_info. Đây thường là dữ liệu công khai ngầm định từ ngữ cảnh, nhưng phải được liên kết với văn bản mã hoá kết quả. Điều này có nghĩa là văn bản mã hoá cho phép bạn xác nhận tính toàn vẹn của thông tin bối cảnh nhưng không có gì đảm bảo tính bí mật hoặc tính xác thực của thông tin đó. Thông tin ngữ cảnh thực tế có thể trống hoặc rỗng, nhưng để đảm bảo giải mã chính xác văn bản mã hoá kết quả, bạn phải cung cấp cùng một giá trị thông tin ngữ cảnh để giải mã.

Việc triển khai cụ thể của phương thức Mã hoá kết hợp có thể liên kết thông tin bối cảnh với văn bản mã hoá theo nhiều cách, ví dụ:

  • Sử dụng context_info làm dữ liệu đầu vào được liên kết cho phương thức mã hoá đối xứng AEAD (xem RFC 5116).
  • Sử dụng context_info làm dữ liệu đầu vào "CtxInfo" cho HKDF (nếu quá trình triển khai sử dụng HKDF làm hàm phái sinh khoá, hãy xem RFC 5869).

Chọn loại khoá

Bạn nên sử dụng loại khoá DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM cho hầu hết các trường hợp sử dụng. Loại khoá này triển khai tiêu chuẩn Mã hoá khoá công khai kết hợp (HPKE) theo quy định trong RFC 9180. HPKE bao gồm cơ chế đóng gói khoá (KEM), hàm phái sinh khoá (KDF) và thuật toán mã hoá đã xác thực với dữ liệu được liên kết (AEAD).

DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM sử dụng cụ thể:

  • KEM: Diffie–Hellman qua Curve25519 với HKDF-SHA-256 để lấy bí mật dùng chung.
  • KDF: HKDF-SHA-256 để lấy bối cảnh của người gửi và người nhận.
  • AEAD: AES-256-GCM với các số chỉ dùng một lần 12 byte được tạo theo tiêu chuẩn HPKE.

Các loại khoá HPKE được hỗ trợ khác bao gồm nhưng không giới hạn ở những loại sau:

  • 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

Hãy xem RFC 9180 để biết thêm thông tin chi tiết về các lựa chọn thuật toán cho KEM, KDF và AEAD.

Mặc dù không còn được đề xuất, nhưng Tink cũng hỗ trợ một số biến thể của ECIES như mô tả trong tiêu chuẩn ISO 18033-2 của Victor Shoup. Dưới đây là một số loại khoá ECIES được hỗ trợ:

  • 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

Thuộc tính tối thiểu

  • Văn bản thuần tuý và thông tin ngữ cảnh có thể có độ dài tuỳ ý (trong phạm vi từ 0 đến 232 byte)
  • Bảo mật trước các cuộc tấn công bằng văn bản mã hoá được chọn thích ứng
  • Bảo mật 128 bit cho các lược đồ dựa trên đường cong elip