Tôi muốn mã hoá dữ liệu

Bạn nên dùng dữ liệu gốc AEAD với loại khoá AES128_GCM cho hầu hết các trường hợp sử dụng mã hoá dữ liệu.

Mã hoá đã xác thực với dữ liệu liên kết (AEAD) là phương thức gốc đơn giản và phù hợp nhất cho hầu hết các trường hợp sử dụng. AEAD cung cấp tính bí mật và tính xác thực, đồng thời đảm bảo rằng các thông báo luôn có các văn bản mã hoá (đầu ra được mã hoá) khác nhau ngay cả khi văn bản thuần tuý (đầu vào để mã hoá) giống nhau. Đây là thuật toán đối xứng, sử dụng một khoá duy nhất cho cả hoạt động mã hoá và giải mã.

Các ví dụ sau đây giúp bạn bắt đầu sử dụng nguyên thuỷ AEAD:

C++

// A command-line utility for testing Tink AEAD.
#include <iostream>
#include <memory>
#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/aead.h"
#include "tink/aead/config_v0.h"
#include "util/util.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, "", "Filename to operate on");
ABSL_FLAG(std::string, output_filename, "", "Output file name");
ABSL_FLAG(std::string, associated_data, "",
          "Associated data for AEAD (default: empty");

namespace {

using ::crypto::tink::Aead;
using ::crypto::tink::KeysetHandle;

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

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// AEAD example CLI implementation.
absl::Status AeadCli(absl::string_view mode, const std::string& keyset_filename,
                     const std::string& input_filename,
                     const std::string& output_filename,
                     absl::string_view associated_data) {
  // Read the keyset from file.
  absl::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();

  // Get the primitive.
  absl::StatusOr<std::unique_ptr<Aead>> aead =
      (*keyset_handle)
          ->GetPrimitive<crypto::tink::Aead>(crypto::tink::ConfigAead2026());
  if (!aead.ok()) return aead.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) {
    absl::StatusOr<std::string> encrypt_result =
        (*aead)->Encrypt(*input_file_content, associated_data);
    if (!encrypt_result.ok()) return encrypt_result.status();
    output = encrypt_result.value();
  } else {  // operation == kDecrypt.
    absl::StatusOr<std::string> decrypt_result =
        (*aead)->Decrypt(*input_file_content, associated_data);
    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 associated_data = absl::GetFlag(FLAGS_associated_data);

  std::clog << "Using keyset from file " << keyset_filename << " to AEAD-"
            << mode << " file " << input_filename << " with associated data '"
            << associated_data << "'." << '\n';
  std::clog << "The resulting output will be written to " << output_filename
            << '\n';

  ABSL_CHECK_OK(tink_cc_examples::AeadCli(mode, keyset_filename, input_filename,
                                          output_filename, associated_data));
  return 0;
}

Go

import (
	"bytes"
	"fmt"
	"log"

	"github.com/tink-crypto/tink-go/v2/aead"
	"github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset"
	"github.com/tink-crypto/tink-go/v2/keyset"
)

func Example() {
	// A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note
	// that this keyset has the secret key information in cleartext.
	jsonKeyset := `{
			"key": [{
					"keyData": {
							"keyMaterialType":
									"SYMMETRIC",
							"typeUrl":
									"type.googleapis.com/google.crypto.tink.AesGcmKey",
							"value":
									"GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
					},
					"keyId": 294406504,
					"outputPrefixType": "TINK",
					"status": "ENABLED"
			}],
			"primaryKeyId": 294406504
	}`

	// Create a keyset handle from the cleartext keyset in the previous
	// step. 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 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://developers.google.com/tink/key-management-overview.
	keysetHandle, err := insecurecleartextkeyset.Read(
		keyset.NewJSONReader(bytes.NewBufferString(jsonKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the AEAD primitive we want to use from the keyset handle.
	primitive, err := aead.New(keysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Use the 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).
	plaintext := []byte("message")
	associatedData := []byte("associated data")
	ciphertext, err := primitive.Encrypt(plaintext, associatedData)
	if err != nil {
		log.Fatal(err)
	}

	// Use the 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 returns an error.
	decrypted, err := primitive.Decrypt(ciphertext, associatedData)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(decrypted))
	// Output: message
}

Java

package aead;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.Aead;
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.aead.AeadConfig;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * A command-line utility for encrypting small files with AEAD.
 *
 * <p>It loads cleartext keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>mode: Can be "encrypt" or "decrypt" to encrypt/decrypt the input to the output.
 *   <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] associated-data: Associated data used for the encryption or decryption.
 */
public final class AeadExample {
  private static final String MODE_ENCRYPT = "encrypt";
  private static final String MODE_DECRYPT = "decrypt";

  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 AeadExample encrypt/decrypt key-file input-file output-file"
              + " [associated-data]");
      System.exit(1);
    }
    String mode = args[0];
    Path keyFile = Paths.get(args[1]);
    Path inputFile = Paths.get(args[2]);
    Path outputFile = Paths.get(args[3]);
    byte[] associatedData = new byte[0];
    if (args.length == 5) {
      associatedData = args[4].getBytes(UTF_8);
    }
    // Register all AEAD key types with the Tink runtime.
    AeadConfig.register();

    // Read the keyset into a KeysetHandle.
    KeysetHandle handle =
        TinkJsonProtoKeysetFormat.parseKeyset(
            new String(Files.readAllBytes(keyFile), UTF_8), InsecureSecretKeyAccess.get());

    // Get the primitive.
    Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class);

    // Use the primitive to encrypt/decrypt files.
    if (MODE_ENCRYPT.equals(mode)) {
      byte[] plaintext = Files.readAllBytes(inputFile);
      byte[] ciphertext = aead.encrypt(plaintext, associatedData);
      Files.write(outputFile, ciphertext);
    } else if (MODE_DECRYPT.equals(mode)) {
      byte[] ciphertext = Files.readAllBytes(inputFile);
      byte[] plaintext = aead.decrypt(ciphertext, associatedData);
      Files.write(outputFile, plaintext);
    } else {
      System.err.println("The first argument must be either encrypt or decrypt, got: " + mode);
      System.exit(1);
    }
  }

  private AeadExample() {}
}

Obj-C

HƯỚNG DẪN

Python

import tink
from tink import aead
from tink import secret_key_access


def example():
  """Encrypt and decrypt using AEAD."""
  # Register the AEAD key managers. This is needed to create an Aead primitive
  # later.
  aead.register()

  # A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note
  # that this keyset has the secret key information in cleartext.
  keyset = r"""{
      "key": [{
          "keyData": {
              "keyMaterialType":
                  "SYMMETRIC",
              "typeUrl":
                  "type.googleapis.com/google.crypto.tink.AesGcmKey",
              "value":
                  "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
          },
          "keyId": 294406504,
          "outputPrefixType": "TINK",
          "status": "ENABLED"
      }],
      "primaryKeyId": 294406504
  }"""

  # Create a keyset handle from the cleartext keyset in the previous
  # step. The keyset handle provides abstract access to the underlying keyset to
  # limit access of the raw key material. WARNING: In practice, it is unlikely
  # you will want to use a cleartext_keyset_handle, as it implies that your key
  # material is passed in cleartext, which is a security risk.
  keyset_handle = tink.json_proto_keyset_format.parse(
      keyset, secret_key_access.TOKEN
  )

  # Retrieve the Aead primitive we want to use from the keyset handle.
  primitive = keyset_handle.primitive(aead.Aead)

  # Use the 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 = primitive.encrypt(b'msg', b'associated_data')

  # Use the 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.
  output = primitive.decrypt(ciphertext, b'associated_data')

AEAD

Phương thức gốc Mã hoá đã xác thực với dữ liệu liên kết (AEAD) là phương thức gốc phổ biến nhất để mã hoá dữ liệu và phù hợp với hầu hết các nhu cầu.

AEAD có các thuộc tính sau:

  • Secrecy: Không có thông tin nào về văn bản thô được biết đến, ngoại trừ độ dài của văn bản đó.
  • Tính xác thực: Không thể thay đổi văn bản thuần tuý được mã hoá làm cơ sở cho văn bản mã hoá mà không bị phát hiện.
  • Đối xứng: Mã hoá văn bản thô và giải mã văn bản mật mã được thực hiện bằng cùng một khoá.
  • Sắp xếp ngẫu nhiên: Mã hoá được sắp xếp ngẫu nhiên. Hai thông báo có cùng văn bản thuần tuý sẽ tạo ra các văn bản mã hoá khác nhau. Kẻ tấn công không thể biết bản mã nào tương ứng với một bản rõ nhất định. Nếu bạn muốn tránh điều này, hãy sử dụng AEAD tất định.

Dữ liệu liên quan

Bạn có thể dùng AEAD để liên kết bản mã với dữ liệu liên kết cụ thể. Giả sử bạn có một cơ sở dữ liệu có các trường user-id và encrypted-medical-history. Trong trường hợp này, bạn có thể dùng user-id làm dữ liệu liên kết khi mã hoá encrypted-medical-history. Điều này ngăn kẻ tấn công di chuyển bệnh sử từ người dùng này sang người dùng khác.

Dữ liệu liên kết là không bắt buộc. Nếu được chỉ định, quá trình giải mã chỉ thành công nếu cùng một dữ liệu liên kết được truyền đến cả lệnh gọi mã hoá và giải mã.

Chọn loại chìa khoá

Mặc dù bạn nên dùng AES128_GCM cho hầu hết các trường hợp sử dụng, nhưng có nhiều loại khoá cho các nhu cầu khác nhau. AES128 cung cấp mức bảo mật 128 bit và AES256 cung cấp mức bảo mật 256 bit.

Hai quy tắc ràng buộc bảo mật đáng chú ý khi chọn một chế độ là:

  1. QPS: Có bao nhiêu thông báo được mã hoá bằng cùng một khoá?
  2. Kích thước thư: Thư có kích thước bao nhiêu?

Các loại khoá được hỗ trợ:

  • AES-CTR-HMAC (AES128_CTR_HMAC_SHA256, AES256_CTR_HMAC_SHA256) có Vectơ khởi tạo (IV) 16 byte là chế độ thận trọng nhất với các giới hạn phù hợp.
    • Cam kết khoá.
  • AES-EAX (AES128_EAX, AES256_EAX) ít bảo thủ hơn một chút và nhanh hơn một chút so với AES128_CTR_HMAC_SHA256.
    • MLGR không cam kết khoá.
  • AES-GCM (AES128_GCM, AES256_GCM) thường là chế độ nhanh nhất với giới hạn nghiêm ngặt nhất về số lượng thông báo (232) và kích thước thông báo (~64 GB mỗi thông báo). Khi vượt quá các giới hạn này, AES-GCM sẽ thất bại thảm hại bằng cách rò rỉ văn bản thuần tuý và phần xác thực của khoá nội bộ AES-GCM.
    • AES-GCM không mạnh [ABN] cũng không cam kết khoá [GLR]. Bạn có thể tạo một văn bản mã hoá có thể giải mã bằng hai khoá khác nhau. Điều này có thể dẫn đến các cuộc tấn công thực tế [DGRW]. Hãy xem xét kỹ mô hình mối đe doạ của bạn nếu khoá do đối thủ chọn.
  • AES-GCM-SIV (AES128_GCM_SIV, AES256_GCM_SIV) [RFC8452] có khả năng chống lại việc sử dụng số chỉ dùng một lần sai cách và có hiệu suất gần bằng AES-GCM. Giới hạn về số lượng thông báo và kích thước thông báo cho AES-GCM-SIV có IV ngẫu nhiên cao hơn nhiều so với giới hạn của AES-GCM. Khi vượt quá các giới hạn này, AES-GCM-SIV sẽ gặp lỗi ít nghiêm trọng hơn: chỉ làm rò rỉ thông tin rằng hai thông báo bằng nhau. AES-GCM-SIV an toàn hơn khi sử dụng, nhưng ít được sử dụng rộng rãi trong thực tế.
    • Giới hạn cho số chỉ dùng một lần ngẫu nhiên:
      • 232 thông báo, trong đó mỗi văn bản thuần tuý có kích thước tối đa là 8 GiB
      • 248 thông báo, trong đó mỗi văn bản thuần tuý có kích thước tối đa là 32 MiB
      • 264 thông báo, trong đó mỗi văn bản thuần tuý có kích thước tối đa là 128 KiB
    • Để sử dụng trong Java, hãy cài đặt Conscrypt.
    • Không cam kết khoá ADGKLS.
  • XChaCha20-Poly1305 (XCHACHA20_POLY1305) có giới hạn về số lượng thư và kích thước thư lớn hơn nhiều so với AES-GCM, nhưng khi không thành công (rất khó xảy ra), thuật toán này cũng làm rò rỉ dữ liệu khoá. Đây không phải là thuật toán được tăng tốc phần cứng, vì vậy, thuật toán này có thể chậm hơn các chế độ AES trong trường hợp có tính năng tăng tốc phần cứng.
    • LGR không cam kết khoá.

Tìm hiểu thêm về định dạng truyền dữ liệu của bản mã AEAD.

Đảm bảo an ninh

Các phương thức triển khai AEAD cung cấp:

  • Bảo mật CCA2.
  • Độ mạnh của chế độ xác thực tối thiểu là 80 bit.
  • Có thể mã hoá ít nhất 232 thông báo với tổng dung lượng là 250 byte. Không có cuộc tấn công nào có tối đa 232 bản rõ đã chọn hoặc bản mã đã chọn có xác suất thành công lớn hơn 2-32.