Dokumen ini secara formal menentukan fungsi matematika yang direpresentasikan oleh
kunci Streaming AES-GCM-HKDF, yang dienkode dalam format proto sebagai
type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
.
Enkripsi ini didasarkan secara longgar pada HRRV151. Untuk analisis keamanan, lihat HS202.
Kunci dan parameter
Kunci dijelaskan oleh bagian berikut (semua ukuran dalam dokumen ini dalam byte):
- \(\mathrm{KeyValue}\), string byte.
- \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
- \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
- \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
Kunci yang valid juga memenuhi properti berikut:
- \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (Nilai ini sama dengan \(\mathrm{len}(\mathrm{Header}) + 16\) seperti yang dijelaskan nanti).
Kunci yang tidak memenuhi salah satu properti ini akan ditolak oleh Tink, baik saat kunci diuraikan maupun 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 terenkripsi.
Membuat header
Kita memilih string acak seragam \(\mathrm{Salt}\) dengan panjang \(\mathrm{DerivedKeySize}\) dan 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. Perhatikan bahwa \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).
Selanjutnya, kita menggunakan HKDF3 dengan fungsi hash yang diberikan oleh \(\mathrm{HkdfHashType}\) dan input \(\mathrm{ikm} := \mathrm{KeyValue}\), \(\mathrm{salt} := \mathrm{Salt}\), dan \(\mathrm{info} := \mathrm{AssociatedData}\), dengan panjang output \(\mathrm{DerivedKeySize}\). Kita menyebut hasilnya \(\mathrm{DerivedKey}\).
Membagi pesan
Pesan \(\mathrm{Msg}\) selanjutnya dibagi menjadi beberapa bagian: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Panjangnya dipilih untuk memenuhi:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\).
- Jika \(n>1\), maka \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\).
- Jika \(n>1\), \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) harus memiliki panjang maksimum sesuai dengan batasan di atas.
\(n\) maksimal \(2^{32}\). Jika tidak, enkripsi akan gagal.
Mengenkripsi blok
Untuk mengenkripsi segmen \(M_i\), kita menghitung \(\mathrm{IV}_i := \mathrm{NoncePrefix}
\| \mathrm{i} \| b\), dengan \(\mathrm{i}\) adalah 4 byte dalam encoding big-endian dan
byte $b$ adalah 0x00
jika $i < n-1$ dan 0x01
jika tidak.
Kemudian, kita mengenkripsi \(M_i\) menggunakan AES-GCM4, dengan kunci \(\mathrm{DerivedKey}\), vektor inisialisasi \(\mathrm{IV}_i\), dan data terkait adalah string kosong. \(C_i\) adalah hasil enkripsi ini (yaitu penyambungan \(C\) dan \(T\) di bagian 5.2.1.2 dari referensi AES-GCM tertaut).
Menggabungkan segmen terenkripsi
Terakhir, semua segmen digabungkan sebagai \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), yang merupakan ciphertext akhir.
Dekripsi
Dekripsi membalik enkripsi. Kita menggunakan header untuk mendapatkan \(\mathrm{NoncePrefix}\), 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 \(M_i\) dapat didekripsi 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_gcm_hkdf_streaming.proto. Kolom version
harus
ditetapkan ke 0. Kemudian, kita melakukan serialisasi 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.AesGcmHkdfStreamingKey
. 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 diuraikan
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 harus ditolak.
Masalah umum
Implementasi fungsi enkripsi di atas diperkirakan tidak aman untuk fork. Lihat Keamanan Fork.
Referensi
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. Enkripsi diautentikasi online dan ketahanan penyalahgunaan penggunaan kembali nonce-nya. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
Hoang, Shen, 2020. Keamanan Enkripsi Streaming di Library Tink Google. https://eprint.iacr.org/2020/1019 ↩
-
RFC 5869. Fungsi Derivasi Kunci Ekstrak-dan-Luaskan (HKDF) berbasis HMAC. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D. Rekomendasi untuk Mode Operasi Cipher Blok: Galois/Counter Mode (GCM) dan GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final ↩