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
-
[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 ↩
-
[HS20] Sicurezza della crittografia in streaming nella libreria Tink di Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] Funzione di derivazione delle chiavi basata su estrazione ed espansione (HKDF) con HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩