Büyük dosyaları veya veri akışlarını şifrelemek istiyorum

Çoğu dosya şifreleme kullanım alanı için AES128_GCM_HKDF_1MB anahtar türüyle Streaming AEAD ilkelini kullanmanızı öneririz.

İlişkili Verilerle Akışla Kimlik Doğrulaması Yapılmış Şifreleme (Akışla AEAD) ilkel işlevi, canlı veri akışlarını veya belleğe sığmayan büyük dosyaları şifrelemek için kullanışlıdır. AEAD'ye benzer şekilde simetriktir ve hem şifreleme hem de şifre çözme için tek bir anahtar kullanır.

Aşağıdaki örnekler, Akış AEAD ilkelini kullanmaya başlamanıza yardımcı olur:

Go

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"

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

func Example() {
	// A keyset created with "tinkey create-keyset --key-template=AES256_CTR_HMAC_SHA256_1MB". Note
	// that this keyset has the secret key information in cleartext.
	jsonKeyset := `{
    "primaryKeyId": 1720777699,
    "key": [{
        "keyData": {
            "typeUrl": "type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey",
            "keyMaterialType": "SYMMETRIC",
            "value": "Eg0IgCAQIBgDIgQIAxAgGiDtesd/4gCnQdTrh+AXodwpm2b6BFJkp043n+8mqx0YGw=="
        },
        "outputPrefixType": "RAW",
        "keyId": 1720777699,
        "status": "ENABLED"
    }]
	}`

	// 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 an 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.
	keysetHandle, err := insecurecleartextkeyset.Read(
		keyset.NewJSONReader(bytes.NewBufferString(jsonKeyset)))
	if err != nil {
		log.Fatal(err)
	}

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

	// Create a file with the plaintext.
	dir, err := os.MkdirTemp("", "streamingaead")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir)
	plaintextPath := filepath.Join(dir, "plaintext")
	if err := os.WriteFile(plaintextPath, []byte("this data needs to be encrypted"), 0666); err != nil {
		log.Fatal(err)
	}
	plaintextFile, err := os.Open(plaintextPath)
	if err != nil {
		log.Fatal(err)
	}

	// associatedData defines the context of the encryption. Here, we include the path of the
	// plaintext file.
	associatedData := []byte("associatedData for " + plaintextPath)

	// Encrypt the plaintext file and write the output to the ciphertext file. In this case the
	// primary key of the keyset will be used (which is also the only key in this example).
	ciphertextPath := filepath.Join(dir, "ciphertext")
	ciphertextFile, err := os.Create(ciphertextPath)
	if err != nil {
		log.Fatal(err)
	}
	w, err := primitive.NewEncryptingWriter(ciphertextFile, associatedData)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := io.Copy(w, plaintextFile); err != nil {
		log.Fatal(err)
	}
	if err := w.Close(); err != nil {
		log.Fatal(err)
	}
	if err := ciphertextFile.Close(); err != nil {
		log.Fatal(err)
	}
	if err := plaintextFile.Close(); err != nil {
		log.Fatal(err)
	}

	// Decrypt the ciphertext file and write the output to the decrypted file. The
	// decryption finds the correct key in the keyset and decrypts the ciphertext.
	// If no key is found or decryption fails, it returns an error.
	ciphertextFile, err = os.Open(ciphertextPath)
	if err != nil {
		log.Fatal(err)
	}
	decryptedPath := filepath.Join(dir, "decrypted")
	decryptedFile, err := os.Create(decryptedPath)
	if err != nil {
		log.Fatal(err)
	}
	r, err := primitive.NewDecryptingReader(ciphertextFile, associatedData)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := io.Copy(decryptedFile, r); err != nil {
		log.Fatal(err)
	}
	if err := decryptedFile.Close(); err != nil {
		log.Fatal(err)
	}
	if err := ciphertextFile.Close(); err != nil {
		log.Fatal(err)
	}

	// Print the content of the decrypted file.
	b, err := os.ReadFile(decryptedPath)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
	// Output: this data needs to be encrypted
}

Java

package streamingaead;

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

import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.StreamingAead;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.streamingaead.StreamingAeadConfig;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.GeneralSecurityException;

/**
 * A command-line utility for encrypting files with Streaming 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 StreamingAeadExample {
  private static final String MODE_ENCRYPT = "encrypt";
  private static final String MODE_DECRYPT = "decrypt";
  private static final int BLOCK_SIZE_IN_BYTES = 8 * 1024;

  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 StreamingAeadExample 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);
    }

    // Initialize Tink: register all Streaming AEAD key types with the Tink runtime
    StreamingAeadConfig.register();

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

    // Get the primitive
    StreamingAead streamingAead =
        handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class);

    // Use the primitive to encrypt/decrypt files
    if (mode.equals(MODE_ENCRYPT)) {
      encryptFile(streamingAead, inputFile, outputFile, associatedData);
    } else if (mode.equals(MODE_DECRYPT)) {
      decryptFile(streamingAead, inputFile, outputFile, associatedData);
    } else {
      System.err.println(
          "The first argument must be either "
              + MODE_ENCRYPT
              + " or "
              + MODE_DECRYPT
              + ", got: "
              + mode);
      System.exit(1);
    }
  }

  private static void encryptFile(
      StreamingAead streamingAead, Path inputFile, Path outputFile, byte[] associatedData)
      throws GeneralSecurityException, IOException {
    try (WritableByteChannel encryptingChannel =
            streamingAead.newEncryptingChannel(
                FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE),
                associatedData);
        FileChannel inputChannel = FileChannel.open(inputFile, StandardOpenOption.READ)) {
      ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK_SIZE_IN_BYTES);
      while (true) {
        int read = inputChannel.read(byteBuffer);
        if (read <= 0) {
          return;
        }
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
          encryptingChannel.write(byteBuffer);
        }
        byteBuffer.clear();
      }
    }
  }

  private static void decryptFile(
      StreamingAead streamingAead, Path inputFile, Path outputFile, byte[] associatedData)
      throws GeneralSecurityException, IOException {
    try (ReadableByteChannel decryptingChannel =
            streamingAead.newDecryptingChannel(
                FileChannel.open(inputFile, StandardOpenOption.READ), associatedData);
        FileChannel outputChannel =
            FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
      ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK_SIZE_IN_BYTES);
      while (true) {
        int read = decryptingChannel.read(byteBuffer);
        if (read <= 0) {
          return;
        }
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
          outputChannel.write(byteBuffer);
        }
        byteBuffer.clear();
      }
    }
  }

  private StreamingAeadExample() {}
}

Python

"""A command-line utility for using streaming AEAD for a file.

It loads cleartext keys from disk - this is not recommended!

It requires 4 arguments (and one optional one):
  mode: either 'encrypt' or 'decrypt'
  keyset_path: name of the file with the keyset to be used for encryption or
    decryption
  input_path: name of the file with the input data to be encrypted or decrypted
  output_path: name of the file to write the ciphertext respectively plaintext
    to
  [optional] associated_data: the associated data used for encryption/decryption
    provided as a string.
"""

from typing import BinaryIO

from absl import app
from absl import flags
from absl import logging
import tink
from tink import secret_key_access
from tink import streaming_aead

FLAGS = flags.FLAGS
BLOCK_SIZE = 1024 * 1024  # The CLI tool will read/write at most 1 MB at once.

flags.DEFINE_enum('mode', None, ['encrypt', 'decrypt'],
                  'Selects if the file should be encrypted or decrypted.')
flags.DEFINE_string('keyset_path', None,
                    'Path to the keyset used for encryption or decryption.')
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,
                    'Associated data used for the encryption or decryption.')


def read_as_blocks(file: BinaryIO):
  """Generator function to read from a file BLOCK_SIZE bytes.

  Args:
    file: The file object to read from.

  Yields:
    Returns up to BLOCK_SIZE bytes from the file.
  """
  while True:
    data = file.read(BLOCK_SIZE)
    # If file was opened in rawIO, EOF is only reached when b'' is returned.
    # pylint: disable=g-explicit-bool-comparison
    if data == b'':
      break
    # pylint: enable=g-explicit-bool-comparison
    yield data


def encrypt_file(input_file: BinaryIO, output_file: BinaryIO,
                 associated_data: bytes,
                 primitive: streaming_aead.StreamingAead):
  """Encrypts a file with the given streaming AEAD primitive.

  Args:
    input_file: File to read from.
    output_file: File to write to.
    associated_data: Associated data provided for the AEAD.
    primitive: The streaming AEAD primitive used for encryption.
  """
  with primitive.new_encrypting_stream(output_file,
                                       associated_data) as enc_stream:
    for data_block in read_as_blocks(input_file):
      enc_stream.write(data_block)


def decrypt_file(input_file: BinaryIO, output_file: BinaryIO,
                 associated_data: bytes,
                 primitive: streaming_aead.StreamingAead):
  """Decrypts a file with the given streaming AEAD primitive.

  This function will cause the program to exit with 1 if the decryption fails.

  Args:
    input_file: File to read from.
    output_file: File to write to.
    associated_data: Associated data provided for the AEAD.
    primitive: The streaming AEAD primitive used for decryption.
  """
  try:
    with primitive.new_decrypting_stream(input_file,
                                         associated_data) as dec_stream:
      for data_block in read_as_blocks(dec_stream):
        output_file.write(data_block)
  except tink.TinkError as e:
    logging.exception('Error decrypting ciphertext: %s', e)
    exit(1)


def main(argv):
  del argv

  associated_data = b'' if not FLAGS.associated_data else bytes(
      FLAGS.associated_data, 'utf-8')

  # Initialise Tink.
  try:
    streaming_aead.register()
  except tink.TinkError as e:
    logging.exception('Error initialising Tink: %s', e)
    return 1

  # Read the keyset into a keyset_handle.
  with open(FLAGS.keyset_path, 'rt') as keyset_file:
    try:
      text = keyset_file.read()
      keyset_handle = tink.json_proto_keyset_format.parse(
          text, secret_key_access.TOKEN
      )
    except tink.TinkError as e:
      logging.exception('Error reading key: %s', e)
      return 1

  # Get the primitive.
  try:
    streaming_aead_primitive = keyset_handle.primitive(
        streaming_aead.StreamingAead)
  except tink.TinkError as e:
    logging.exception('Error creating streaming AEAD primitive from keyset: %s',
                      e)
    return 1

  # Encrypt or decrypt the file.
  with open(FLAGS.input_path, 'rb') as input_file:
    with open(FLAGS.output_path, 'wb') as output_file:
      if FLAGS.mode == 'encrypt':
        encrypt_file(input_file, output_file, associated_data,
                     streaming_aead_primitive)
      elif FLAGS.mode == 'decrypt':
        decrypt_file(input_file, output_file, associated_data,
                     streaming_aead_primitive)


if __name__ == '__main__':
  flags.mark_flag_as_required('mode')
  flags.mark_flag_as_required('keyset_path')
  flags.mark_flag_as_required('input_path')
  flags.mark_flag_as_required('output_path')
  app.run(main)

Akış AEAD

Akış AEAD ilkel, akış verileri için kimliği doğrulanmış şifreleme sağlar. Şifrelenecek veriler tek bir adımda işlenemeyecek kadar büyük olduğunda kullanışlıdır. Büyük dosyaların veya canlı veri akışlarının şifrelenmesi, bu API'nin tipik kullanım alanlarından biridir.

Şifreleme, şifrelenmiş metin içindeki konumlarına bağlı olan ve kaldırılamayan veya yeniden sıralanamayan segmentler halinde yapılır. Bir şifre metnindeki segmentler başka bir şifre metnine eklenemez. Mevcut bir şifre metnini değiştirmek için veri akışının tamamının yeniden şifrelenmesi gerekir.1

Şifrelenmiş metnin yalnızca bir kısmı aynı anda şifresi çözülüp kimliği doğrulandığından şifre çözme işlemi hızlıdır. Şifrelenmiş metnin tamamı işlenmeden kısmi düz metinler elde edilebilir.

Akış AEAD uygulamaları AEAD tanımını karşılar ve nOAE güvenlidir. Bu mülklerin özellikleri şunlardır:

  • Secrecy: Açık metinle ilgili olarak uzunluğu dışında hiçbir şey bilinmez.
  • Authenticity: Şifrelenmiş metnin altındaki şifrelenmemiş metnin değiştirilmesi fark edilmeden mümkün değildir.
  • Symmetric: Açık metnin şifrelenmesi ve şifrelenmiş metnin çözülmesi aynı anahtarla yapılır.
  • Rastgele hale getirme: Şifreleme rastgele hale getirilir. Aynı açık metne sahip iki mesaj farklı şifre metinleri oluşturur. Saldırganlar, belirli bir şifrelenmemiş metne hangi şifrelenmiş metnin karşılık geldiğini bilemez.

İlişkili veriler

Akış AEAD ilkel, şifre metnini belirli ilişkili verilere bağlamak için kullanılabilir. user-id ve encrypted-medical-history alanlarını içeren bir veritabanınız olduğunu varsayalım: Bu senaryoda, encrypted-medical-history şifrelenirken user-id ilişkili veri olarak kullanılabilir. Bu, saldırganların tıbbi geçmişi bir kullanıcıdan diğerine taşımasını engeller.

Anahtar türü seçin

Çoğu kullanım için AES128_GCM_HKDF_1MB'yi öneririz. Genel olarak:

  • AES-GCM-HKDF
    • AES128_GCM_HKDF_1MB (veya AES256_GCM_HKDF_1MB) daha hızlı seçenektir. Her biri 264 bayta kadar olan 264 dosyayı şifreleyebilir. Şifreleme ve şifre çözme işlemi sırasında yaklaşık 1 MB bellek tüketilir.
    • AES128_GCM_HKDF_4KB yaklaşık 4 KB bellek tüketir ve sisteminizde çok fazla bellek yoksa iyi bir seçimdir.
  • AES-CTR HMAC
    • AES128_CTR_HMAC_SHA256_1MB (veya AES256_CTR_HMAC_SHA256_1MB) daha muhafazakar bir seçenektir.

Güvenlik garantileri

Akış AEAD uygulamaları şunları sunar:

  • CCA2 güvenliği.
  • En az 80 bit kimlik doğrulama gücü.
  • En az 264 iletiyi3 toplam 251 bayt2 ile şifreleme olanağı . 232'ye kadar seçili düz metin veya seçili şifre metni içeren hiçbir saldırının başarı olasılığı 2-32'den büyük değildir.

  1. Bu kısıtlamanın nedenlerinden biri AES-GCM şifresinin kullanılmasıdır. Aynı konumda farklı bir açık metin segmentinin şifrelenmesi, IV'nin yeniden kullanılmasına eşdeğer olur ve bu da AES-GCM'nin güvenlik garantilerini ihlal eder. Bunun bir diğer nedeni de, saldırganın dosyanın önceki bir sürümünü tespit edilmeden geri yüklemeye çalışabileceği geri alma saldırılarını önlemesidir. 

  2. Her biri segment_size - tag_size bayt düz metin içeren 232 segment desteklenir. 1 MB'lık segmentler için toplam açık metin boyutu 232 * (220-16) ~= 251 bayttır. 

  3. Türetilmiş anahtar (128 bit) ve tek seferlik önek (bağımsız rastgele 7 baytlık değer) kombinasyonu tekrarlandığında AEAD aktarımı güvenli olmaz. 184 bit çarpışma direnci sunuyoruz. Başarı olasılığının 2-32'den az olmasını istiyorsak bu yaklaşık olarak 264 mesaja karşılık gelir.