Dokumen ini secara formal menentukan fungsi matematika yang diwakili oleh
kunci Streaming HMAC AES-CTR (dienkode dalam format proto sebagai
type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
).
Enkripsi ini didasarkan secara longgar pada [HRRV15]1. Untuk analisis keamanan, lihat [HS20]2. Perhatikan juga bahwa pengujian lintas bahasa Tink
memiliki pengujian
aes_ctr_hmac_streaming_key_test.py yang
berisi test_manually_created_test_vector
dengan panduan lengkap tentang cara
mendapatkan ciphertext.
Kunci dan parameter
Kunci dijelaskan oleh bagian berikut (semua ukuran dalam dokumen ini dalam byte):
- \(\mathrm{InitialKeyMaterial}\), string byte: materi kunci awal.
- \(\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}\).
Kunci yang valid juga memenuhi properti berikut:
- \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
- Jika \(\mathrm{HmacHashType} = \mathrm{SHA1}\) maka \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
- Jika \(\mathrm{HmacHashType} = \mathrm{SHA256}\) maka \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
- Jika \(\mathrm{HmacHashType} = \mathrm{SHA512}\) maka \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (Nilai ini sama dengan \(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) seperti yang dijelaskan nanti).
Kunci yang tidak memenuhi salah satu properti ini akan ditolak oleh Tink (baik saat kunci diuraikan atau saat primitif yang sesuai dibuat).
Fungsi enkripsi
Untuk mengenkripsi pesan \(\mathrm{Msg}\) dengan data terkait \(\mathrm{AssociatedData}\), kita membuat header, membagi pesan menjadi segmen, mengenkripsi setiap segmen, dan menyambungkan segmen. Kami menjelaskan langkah-langkah ini di bawah.
Membuat header
Untuk membuat header, pertama-tama kita memilih string acak merata \(\mathrm{Salt}\) dengan panjang \(\mathrm{DerivedKeySize}\). Selanjutnya, kita memilih string acak seragam \(\mathrm{NoncePrefix}\) dengan panjang 7.
Kemudian, kita menetapkan \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), dengan panjang header dienkode sebagai satu byte. Kita perhatikan bahwa \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).
Selanjutnya, kita menggunakan HKDF3 dengan fungsi hash \(\mathrm{HkdfHashType}\) untuk menghitung materi kunci dengan panjang \(\mathrm{DerivedKeySize} + 32\) untuk pesan ini: \(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Input digunakan dalam input masing-masing yang sesuai dari \(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) adalah \(\mathrm{ikm}\), \(\mathrm{Salt}\) adalah garam, dan \(\mathrm{AssociatedData}\) digunakan sebagai \(\mathrm{info}\).
String \(k\) kemudian dibagi menjadi dua bagian \(k_1 \| k_2 := k\), sehingga \(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) dan \(\mathrm{len}(k_2) = 32\).
Memisahkan pesan
Pesan \(M\) selanjutnya dibagi menjadi beberapa bagian: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Panjangnya dipilih agar memenuhi:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
- Jika \(n > 1\), maka \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
- Jika \(n > 1\), \(M_{0}, \ldots, M_{n-2}\) harus memiliki panjang maksimum sesuai dengan batasan di atas.
Dalam pemisahan ini, \(n\) maksimal dapat berupa \(2^{32}\). Jika tidak, enkripsi akan gagal.
Mengenkripsi blok
Untuk mengenkripsi segmen \(M_i\), pertama-tama kita menghitung
\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\),
tempat kita mengenkode \(\mathrm{i}\) dalam 4
byte menggunakan encoding big-endian, dan menetapkan byte $b$ menjadi 0x00
jika $i < n-1$
dan 0x01
jika tidak.
Kemudian, kita mengenkripsi \(M_i\) menggunakan kunci AES CTR \(k_1\), dan vektor inisialisasi \(\mathrm{IV}_i\). Dengan kata lain, input untuk pemanggilan AES adalah \(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\) dengan \(\mathrm{IV}_i\) ditafsirkan sebagai bilangan bulat big-endian. Tindakan ini akan menghasilkan \(C'_i\).
Kita menghitung tag menggunakan HMAC dengan fungsi hash yang diberikan oleh \(\mathrm{HmacHashType}\) dan dengan kunci \(k_2\) di atas penyambungan \(\mathrm{IV}_i \| C'_i\).
Kemudian, kita menggabungkan ciphertext diikuti dengan tag untuk mendapatkan \(C_i\).
Menggabungkan segmen
Terakhir, semua segmen digabungkan sebagai \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), yang merupakan ciphertext akhir.
Fungsi dekripsi
Dekripsi hanya membalik enkripsi. Kita menggunakan header untuk mendapatkan nonce, dan mendekripsi setiap segmen ciphertext satu per satu.
API dapat (dan biasanya) mengizinkan akses acak, atau akses ke awal file tanpa memeriksa akhir file. Hal ini disengaja, karena Anda dapat mendekripsi \(M_i\) dari \(C_i\), tanpa mendekripsi semua blok ciphertext sebelumnya dan yang tersisa.
Namun, API harus berhati-hati agar tidak mengizinkan pengguna untuk mengacaukan error akhir file dan dekripsi: dalam kedua kasus tersebut, API mungkin harus menampilkan error, dan mengabaikan perbedaan dapat menyebabkan penyerang dapat secara efektif memotong file.
Serialisasi dan penguraian kunci
Untuk melakukan serialisasi kunci dalam format "Tink Proto", pertama-tama kita memetakan parameter
dengan cara yang jelas ke dalam proto yang diberikan di
aes_ctr_hmac_streaming.proto.
Kolom version
harus ditetapkan ke 0.
Kemudian, kita melakukan serialisasi ini menggunakan serialisasi proto normal, dan menyematkan string
yang dihasilkan dalam nilai kolom proto
KeyData. Kita menetapkan kolom type_url
ke type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
.
Kemudian, kita menetapkan key_material_type
ke SYMMETRIC
, dan menyematkannya ke dalam kumpulan kunci. Kita
biasanya menetapkan output_prefix_type
ke RAW
. Pengecualian adalah jika kunci
diurai dengan nilai yang berbeda yang ditetapkan untuk output_prefix_type
,
Tink dapat menulis RAW
atau nilai sebelumnya.
Untuk mengurai kunci, kita membalikkan proses di atas (dengan cara biasa saat mengurai proto). Kolom key_material_type
akan diabaikan. Nilai
output_prefix_type
dapat diabaikan, atau kunci yang
memiliki output_prefix_type
yang berbeda dari RAW
dapat ditolak.
Kunci yang memiliki version
yang berbeda dari 0 akan ditolak.
Referensi
-
[HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Enkripsi terautentikasi online dan ketahanannya terhadap penyalahgunaan penggunaan kembali nonce. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
[HS20] Keamanan Enkripsi Streaming di Library Tink Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] Fungsi Derivation Kunci Ekstrak-dan-Luaskan (HKDF) berbasis HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩