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 secara longgar didasarkan pada HRRV151. Untuk analisis keamanan, kami merujuk pada HS202.
Kunci dan parameter
Kunci dijelaskan dalam bagian-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\) (Ini sama dengan \(\mathrm{len}(\mathrm{Header}) + 16\) seperti yang dijelaskan nanti).
Kunci yang tidak memenuhi properti ini akan ditolak oleh Tink, saat kunci diurai atau saat primitif yang sesuai dibuat.
Fungsi enkripsi
Untuk mengenkripsi pesan \(\mathrm{Msg}\) dengan data terkait \(\mathrm{AssociatedData}\), kita membuat header, membagi pesan menjadi beberapa 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}\). Kami menyebut hasilnya \(\mathrm{DerivedKey}\).
Memisahkan pesan
Selanjutnya, pesan \(\mathrm{Msg}\) akan dibagi menjadi beberapa bagian: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Durasinya 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\), maka \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) harus memiliki panjang maksimal sesuai dengan batasan di atas.
\(n\) mungkin maksimal \(2^{32}\). Jika tidak, enkripsi akan gagal.
Mengenkripsi blok
Untuk mengenkripsi segmen \(M_i\), kami menghitung \(\mathrm{IV}_i := \mathrm{NoncePrefix}
\| \mathrm{i} \| b\), dengan \(\mathrm{i}\) 4 byte dalam encoding big-end dan
byte $b$ adalah 0x00
jika $i < n-1$ dan 0x01
sebaliknya.
Kemudian, kami mengenkripsi \(M_i\) menggunakan AES-GCM4, dengan kuncinya adalah \(\mathrm{DerivedKey}\), vektor inisialisasi adalah \(\mathrm{IV}_i\), dan data terkait adalah string kosong. \(C_i\) adalah hasil dari enkripsi ini (yaitu penyambungan \(C\) dan \(T\) di bagian 5.2.1.2 dari referensi AES-GCM tertaut).
Menggabungkan segmen yang dienkripsi
Terakhir, semua segmen digabungkan sebagai \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), yang merupakan ciphertext akhir.
Dekripsi
Dekripsi akan membalikkan 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 bagian akhir file. Hal ini disengaja karena dapat didekripsi \(M_i\) dari \(C_i\), tanpa mendekripsi semua blok ciphertext sebelumnya dan sisa.
Namun, API harus berhati-hati agar tidak mengizinkan pengguna salah membedakan error akhir file dan dekripsi: dalam kedua kasus, API mungkin harus menampilkan error, dan mengabaikan perbedaannya dapat menyebabkan penyerang dapat memotong file secara efektif.
Serialisasi dan penguraian kunci
Untuk membuat serialisasi kunci dalam format "Tink Proto", pertama-tama kita akan memetakan parameter dengan cara yang jelas ke dalam proto yang diberikan di aes_gcm_hkdf_streaming.proto. Kolom version
harus
ditetapkan ke 0. Kemudian, kami 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 keyset. Kita biasanya menetapkan
output_prefix_type
ke RAW
. Pengecualiannya adalah jika kunci diuraikan
dengan nilai yang ditetapkan untuk output_prefix_type
, Tink dapat menulis RAW
atau nilai sebelumnya.
Untuk mengurai kunci, kita membalikkan proses di atas (dengan cara yang biasa saat mengurai
proto). Kolom key_material_type
diabaikan. Nilai output_prefix_type
dapat diabaikan, atau kunci yang memiliki output_prefix_type
yang berbeda dengan RAW
dapat ditolak. Kunci yang memiliki
version
yang berbeda dengan 0 harus ditolak.
Masalah umum
Implementasi fungsi enkripsi di atas diharapkan tidak aman untuk fork. Lihat Keamanan Fork.
Referensi
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. Enkripsi yang diautentikasi online dan ketahanannya terhadap penyalahgunaan nonce-reuse. 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. Extract-and-Expand Key Derivation Function (HKDF) berbasis HMAC. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D. Rekomendasi untuk Mode Operasi Block Cipher: Galois/Counter Mode (GCM) dan GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final ↩