AEAD streaming HMAC AES-CTR

Questo documento definisce formalmente la funzione matematica rappresentata dalle chiavi di streaming AES-CTR HMAC (codificate in formato proto come type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey).

Questa crittografia è genericamente basata su [HRRV15]1. Per un'analisi della sicurezza, fai riferimento a [HS20]2. Tieni inoltre presente che i test in più lingue di Tink hanno un test aes_ctr_hmac_streaming_key_test.py che contiene test_manually_created_test_vector con una procedura dettagliata completa su come ottenere un testo crittografato.

Chiave e parametri

Le chiavi sono descritte nelle seguenti parti (tutte le dimensioni in questo documento sono espresse in byte):

  • \(\mathrm{InitialKeyMaterial}\), una stringa di byte: il materiale iniziale della chiave.
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacTagSize} \in \mathbb{N}\).

Le chiavi valide soddisfano anche le seguenti proprietà:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA1}\) allora \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA256}\) allora \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA512}\) allora \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (equivale a\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) come spiegato più avanti).

Le chiavi che non soddisfano nessuna di queste proprietà vengono rifiutate da Tink (quando la chiave viene analizzata o creata la primitiva corrispondente).

Funzione di crittografia

Per criptare un messaggio \(\mathrm{Msg}\) con i dati associati\(\mathrm{AssociatedData}\), creiamo un'intestazione, suddividiamo il messaggio in segmenti, criptiamo ogni segmento e li concateniamo. Questi passaggi vengono spiegati di seguito.

Creazione dell'intestazione

Per creare l'intestazione, scegliamo innanzitutto una stringa casuale \(\mathrm{Salt}\) uniforme di lunghezza \(\mathrm{DerivedKeySize}\). Poi scegliamo una stringa casuale uniforme \(\mathrm{NoncePrefix}\) di lunghezza 7.

Quindi impostiamo \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), in cui la lunghezza dell'intestazione è codificata come un singolo byte. Abbiamo notato che: \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Quindi utilizziamo HKDF3 con la funzione hash \(\mathrm{HkdfHashType}\) per calcolare il materiale della lunghezza della chiave \(\mathrm{DerivedKeySize} + 32\) per questo messaggio: \(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Gli input vengono utilizzati nei rispettivi input corrispondenti di \(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) is \(\mathrm{ikm}\), \(\mathrm{Salt}\) è il sale e \(\mathrm{AssociatedData}\) è utilizzato come \(\mathrm{info}\).

La stringa \(k\) viene quindi divisa in due parti \(k_1 \| k_2 := k\), in modo che\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) e \(\mathrm{len}(k_2) = 32\).

Suddividere il messaggio

Il messaggio \(M\) viene poi suddiviso in parti: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).

Le loro lunghezze vengono scelte in modo da soddisfare:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
  • Se \(n > 1\), allora \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Se \(n > 1\), \(M_{0}, \ldots, M_{n-2}\) deve avere una lunghezza massima in base ai vincoli sopra indicati.

In questa suddivisione, \(n\) può essere al massimo \(2^{32}\). In caso contrario, la crittografia non va a buon fine.

Crittografia dei blocchi

Per criptare il segmento \(M_i\), per prima cosa calcoliamo \(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), in cui codifichiamo \(\mathrm{i}\) in 4 byte utilizzando la codifica big-endian, e impostiamo il byte $b$ su 0x00 se $i < n-1$ e 0x01 altrimenti.

Successivamente, eseguiamo la crittografia \(M_i\) utilizzando la chiave CTR AES \(k_1\)e il vettore di inizializzazione\(\mathrm{IV}_i\). In altre parole, gli input delle chiamate all'algoritmo AES vengono \(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\) dove \(\mathrm{IV}_i\) viene interpretato come numero intero big-endian. In questo modo, si ottengono \(C'_i\).

Calcoliamo il tag utilizzando HMAC con la funzione hash fornita da \(\mathrm{HmacHashType}\) e con la chiave \(k_2\) per la concatenazione \(\mathrm{IV}_i \| C'_i\).

Quindi concateniamo il testo crittografato seguito dal tag per ottenere \(C_i\).

Concatena i segmenti

Infine, tutti i segmenti sono concatenati come\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), che rappresenta il testo crittografato finale.

Funzione di decrittografia

La decrittografia inverte semplicemente la crittografia. Utilizziamo l'intestazione per ottenere il nonce e decriptamo singolarmente di ogni segmento di testo crittografato.

Le API possono consentire l'accesso casuale o l'accesso all'inizio di un file senza esaminare la fine del file. Questa operazione è intenzionale, poiché è possibile decriptare \(M_i\) da \(C_i\), senza decriptare tutti i blocchi di testo crittografato precedenti e rimanenti.

Tuttavia, le API devono fare attenzione a non consentire agli utenti di confondere gli errori di fine file e di decriptazione: in entrambi i casi, è probabile che l'API debba restituire un errore e ignorare la differenza può portare un utente malintenzionato a troncare efficacemente i file.

Serializzazione e analisi delle chiavi

Per serializzare una chiave nel formato "Tink Proto", innanzitutto mappamo i parametri in modo ovvio nel proto dato in aes_ctr_hmac_streaming.proto. Il campo version deve essere impostato su 0. Quindi serializziamo il codice utilizzando la normale serializzazione del proto e incorporiamo la stringa risultante nel valore del campo di un proto KeyData. Impostiamo il campo type_url su type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Quindi impostiamo key_material_type su SYMMETRIC e lo incorporiamo in un set di chiavi. Generalmente impostiamo output_prefix_type su RAW. L'eccezione è che se la chiave è stata analizzata con un valore diverso impostato per output_prefix_type, Tink può scrivere RAW o il valore precedente.

Per analizzare una chiave, invertiamo la procedura descritta in precedenza (come di consueto durante l'analisi dei proto). Il campo key_material_type viene ignorato. Il valore di output_prefix_type può essere ignorato oppure le chiavi con output_prefix_type diverso da RAW possono essere rifiutate. Le chiavi con un valore version diverso da 0 vengono rifiutate.

Riferimenti


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Crittografia autenticata online e relativa resistenza all'uso improprio senza riutilizzo. CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Sicurezza della crittografia dei flussi di dati nella libreria Tink di Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 

  3. [HKDF] HKDF (Extract-and-expand Key Derivation Function) basato su HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869