AEAD streaming HMAC AES-CTR

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

Questa crittografia si basa liberamente su [HRRV15]1. Per un'analisi della sicurezza, consulta [HS20]2. Tieni inoltre presente che i test cross-language di Tink includono 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 cifrato.

Chiave e parametri

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

  • \(\mathrm{InitialKeyMaterial}\), una stringa di byte: il materiale della chiave iniziale.
  • \(\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 inoltre 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\) (è uguale a \(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , come spiegato di seguito).

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

Funzione di crittografia

Per criptare un messaggio \(\mathrm{Msg}\) con i dati associati \(\mathrm{AssociatedData}\), creiamo un'intestazione, dividiamo il messaggio in segmenti, criptamo ogni segmento e concatenamo i segmenti. Spieghiamo questi passaggi di seguito.

Creazione dell'intestazione

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

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

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

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

Suddivisione del messaggio

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

Le lunghezze sono scelte in modo da soddisfare i seguenti requisiti:

  • \(\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 riportati sopra.

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\), calcoliamo prima\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), dove codifichiamo \(\mathrm{i}\) in 4 byte utilizzando la codifica big-endian e impostiamo il byte $b$ su 0x00 se $i < n-1$ e 0x01 in caso contrario.

Poi criptamo \(M_i\) utilizzando la chiave AES CTR \(k_1\)e il vettore di inizializzazione\(\mathrm{IV}_i\). In altre parole, gli input alle invocazioni di AES sono \(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\) dove \(\mathrm{IV}_i\) è interpretato come numero intero big-endian. Il risultato è \(C'_i\).

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

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

Concatena i segmenti

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

Funzione di decrittografia

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

Le API possono (e in genere lo fanno) consentire l'accesso casuale o all'inizio di un file senza ispezionare la fine del file. Questo è intenzionale, poiché è possibile decriptare \(M_i\) da \(C_i\), senza decriptare tutti i blocchi di testo cifrato precedenti e rimanenti.

Tuttavia, le API devono fare attenzione a non consentire agli utenti di confondere gli errori di fine file e di decrittografia: in entrambi i casi l'API probabilmente deve restituire un errore e ignorare la differenza può portare un avversario a essere in grado di troncare efficacemente i file.

Serializzazione e analisi delle chiavi

Per serializzare una chiave nel formato "Tink Proto", mappiamo prima i parametri nel modo ovvio nel proto fornito in aes_ctr_hmac_streaming.proto. Il campo version deve essere impostato su 0. Lo serializziamo quindi utilizzando la normale serializzazione di 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. Impostiamo quindi key_material_type su SYMMETRIC e lo incorporiamo in un set di chiavi. Generalmente, imposta 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 sopra (come di consueto per l'analisi di 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. La crittografia con autenticazione online e la sua resistenza all'uso improprio del riutilizzo del nonce. CRYPTO 2015. https://eprint.iacr.org/2015/189 

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

  3. [HKDF] Funzione di derivazione delle chiavi basata su estrazione ed espansione (HKDF) con HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869