Voglio criptare file o stream di dati di grandi dimensioni

Per la maggior parte dei casi d'uso di crittografia dei file, consigliamo la primitiva Streaming AEAD con il tipo di chiave AES128_GCM_HKDF_1MB.

La primitiva Streaming Authenticated Encryption with Associated Data (Streaming AEAD) è utile per criptare flussi di dati in tempo reale o file di grandi dimensioni che non entrano in memoria. Come AEAD, è simmetrica e utilizza una singola chiave sia per la crittografia che per la decrittografia.

Gli esempi riportati di seguito ti consentono di iniziare a utilizzare la primitiva Streaming AEAD:

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://developers.google.com/tink/key-management-overview.
	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)

Streaming AEAD

La primitiva Streaming AEAD fornisce la crittografia autenticata per lo streaming dei dati. È utile quando i dati da criptare sono troppo grandi per essere elaborati in un solo passaggio. I casi d'uso tipici includono la crittografia di file di grandi dimensioni o flussi di dati in tempo reale.

La crittografia viene eseguita in segmenti, che sono associati alla loro posizione all'interno di un testo cifrato e non possono essere rimossi o riordinati. I segmenti di un testo cifrato non possono essere inseriti in un altro testo cifrato. Per modificare un testo crittografato esistente, l'intero stream di dati deve essere ricriptato.1

La decrittografia è rapida perché solo una parte del testo cifrato viene decrittografata e autenticata alla volta. I testi non crittografati parziali sono ottenibili senza elaborare l'intero testo crittografato.

Le implementazioni AEAD di streaming soddisfano la definizione di AEAD e sono sicure nOAE. Hanno le seguenti proprietà:

  • Secrecy: non si conosce nulla del testo non crittografato, tranne la sua lunghezza.
  • Autenticità: è impossibile modificare il testo non crittografato sottostante il testo crittografato senza che ciò venga rilevato.
  • Simmetrica: la crittografia del testo non crittografato e la decrittografia del testo crittografato vengono eseguite con la stessa chiave.
  • Randomizzazione: la crittografia è randomizzata. Due messaggi con lo stesso testo non crittografato producono testi crittografati diversi. Gli aggressori non possono sapere quale testo cifrato corrisponde a un determinato testo non crittografato.

Dati associati

La primitiva Streaming AEAD può essere utilizzata per collegare il testo cifrato a dati associati specifici. Supponi di avere un database con i campi user-id e encrypted-medical-history: in questo scenario, user-id può essere utilizzato come dati associati durante la crittografia di encrypted-medical-history. In questo modo un malintenzionato non può spostare la storia clinica da un utente a un altro.

Scegli un tipo di chiave

Consigliamo AES128_GCM_HKDF_1MB per la maggior parte degli utilizzi. In generale:

  • AES-GCM-HKDF
    • AES128_GCM_HKDF_1MB (o AES256_GCM_HKDF_1MB) è l'opzione più veloce. Può crittografare 264 file con un massimo di 264 byte ciascuno. Durante la procedura di crittografia e decrittografia viene consumata circa 1 MB di memoria.
    • AES128_GCM_HKDF_4KB utilizza circa 4 KB di memoria ed è una buona scelta se il tuo sistema non ha molta memoria.
  • AES-CTR HMAC
    • AES128_CTR_HMAC_SHA256_1MB (o AES256_CTR_HMAC_SHA256_1MB) è un'opzione più conservativa.

Garanzie di sicurezza

Le implementazioni AEAD in streaming offrono:

  • Sicurezza CCA2.
  • Efficacia dell'autenticazione di almeno 80 bit.
  • La possibilità di criptare almeno 264 messaggi3 con un totale di 251 byte2 . Nessun attacco con un massimo di 232 testi non crittografati scelti o testi crittografati scelti ha una probabilità di successo maggiore di 2-32.

  1. Un motivo di questa limitazione è l'utilizzo della crittografia AES-GCM. Criptare un segmento di testo non crittografato diverso nella stessa posizione equivale a riutilizzare il vettore di inizializzazione, il che viola le garanzie di sicurezza di AES-GCM. Un altro motivo è che questo impedisce gli attacchi di rollback, in cui l'autore dell'attacco potrebbe tentare di ripristinare una versione precedente del file senza essere rilevato. ↩

  2. Sono supportati 232 segmenti, ognuno dei quali contiene segment_size - tag_size byte di testo normale. Per i segmenti da 1 MB, la dimensione totale del testo non crittografato è 232 * (220-16) ~= 251 byte. ↩

  3. Lo streaming AEAD diventa non sicuro quando viene ripetuta una combinazione di chiave derivata (128 bit) e prefisso nonce (valore casuale indipendente di 7 byte). Abbiamo una resistenza alle collisioni di 184 bit, che si traduce approssimativamente in 264 messaggi se vogliamo che la probabilità di successo sia inferiore a 2-32. ↩