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
-
[HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Crittografia autenticata online e relativa resistenza all'uso improprio senza riutilizzo. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
[HS20] Sicurezza della crittografia dei flussi di dati nella libreria Tink di Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] HKDF (Extract-and-expand Key Derivation Function) basato su HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩