Bagaimana server dapat menyimpan dan menyinkronkan rahasia kita tanpa memegang kunci untuk membacanya? Kami akan mengikuti cara Confidex membuat kuncinya, melindunginya dalam gembok, dan menggunakannya untuk mengenkripsi data dan memverifikasi perubahan.
Gembok
Hanya Sang Terpilih yang dapat membuka pintu
Matriks Dimuat Ulang
Bayangkan kita sedang membangun vault virtual untuk menyimpan data rahasia. Itu harus menyimpan data itu di laptop setelah aplikasi ditutup dan membiarkan kita membukanya di perangkat lain. Lemari besi itu memerlukan kuncinya sendiri. Kita harus menyimpannya sehingga penyalinan file tidak memungkinkan pihak luar membaca atau mengubah isinya.
lock melindungi kunci: file terenkripsi kecil. Untuk membukanya diperlukan kata sandi utama dan kode rahasia, secret_key; tidak ada di file itu sendiri. Kita bisa mencetak seluruh kunci di koran pagi. Pembaca akan mendapatkan salinannya, tetapi menerbitkannya tidak akan mengungkapkan kunci pribadinya.
Misalkan orang luar mendapatkan file ini dan bahkan mengetahui kata sandi utama. Mereka sekarang dapat mengulangi setiap penghitungan yang hanya memerlukan kata sandi, namun penghitungan tersebut tidak akan menghasilkan kunci yang membuka gembok: secret_key juga merupakan masukan. File tersebut tidak berisi pemeriksaan kata sandi terpisah saja. Membukanya masih memerlukan kode rahasia yang tidak diketahui.
Kode tersebut berasal dari data acak secara kriptografis; seseorang tidak menciptakannya. Selama secret_key tidak diketahui, bahkan kata sandi utama yang ditebak dengan benar tidak akan membuka kunci: penyerang masih kekurangan rahasia acak. Oleh karena itu, kata sandi yang lemah saja tidak mengubah salinan server menjadi serangan kamus praktis.
Jika penyerang juga mendapatkan secret_key, mereka dapat menguji tebakan kata sandi utama secara offline. Ini mahal, tetapi kesulitannya tergantung pada kekuatan kata sandinya.
Kunci
Tuan-tuan, apa fungsi kunci? Kunci… membuka sesuatu?
Pirates of the Caribbean – Peti Orang Mati
Aplikasi memperoleh 32 byte acak dari generator kriptografi sistem operasi dan mempersiapkannya untuk digunakan sebagai kunci. Byte yang disiapkan ini adalah kunci master privat, master_key.
Aplikasi selalu mendapatkan kunci yang sama dari master_key: mengulangi penghitungan yang sama pada byte yang sama akan memberikan hasil yang sama. Oleh karena itu, laptop dan telepon dapat memulihkan kunci yang identik. Untuk mendapatkan master_key di perangkat baru, aplikasi harus membuka kunci terlebih dahulu dengan kata sandi utama dan secret_key.
Pasangan pertama: enkripsi
Saya adalah Keymaster.
Penghancur hantu
Untuk menyimpan data terenkripsi, aplikasi menghitung kunci publik dari master_key: secret_key. Itu dapat mengenkripsi data untuk brankas, sementara dekripsi memerlukan master_key.
“Publik” menggambarkan peran kunci ini dalam algoritme, bukan kewajiban untuk memberikannya kepada semua orang. Di Confidex, data tersebut tetap berada di tangan pengguna. Ini juga merupakan rahasia kedua yang diperlukan untuk membuka kunci, sehingga tetap bersifat lokal dan tidak dikirim melalui jaringan. Dengan sendirinya, secret_key memungkinkan seseorang mengenkripsi data untuk brankas, namun tidak mendekripsinya atau menandatangani perubahan atas namanya.
X25519 adalah algoritme yang menggunakan master_key dan menghitung secret_key. Memulihkan master_key dari secret_key secara praktis tidak mungkin dilakukan. Serangan klasik terhadap superkomputer imajiner yang melakukan satu miliar miliar operasi kurva per detik akan memakan waktu rata-rata sekitar 2,3 triliun tahun (perkiraan SafeCurves).
Pasangan kedua: tanda tangan
Satu-satunya yang asli adalah tanda tangannya
Tangkap Aku Jika Kamu Bisa
Pasangan pertama membantu menyembunyikan isinya. Kami sekarang memerlukan cara untuk mengonfirmasi bahwa pemilik brankas mengizinkan perubahan. Server juga harus bisa mengecek tanda tangan, jadi kita perlu mengirimkannya kunci verifikasi publik. Bagian publik pasangan pertama adalah secret_key, yang tidak boleh diterima oleh server. Oleh karena itu kami membuat pasangan kunci permanen kedua untuk tanda tangan.
Daripada menyimpan rahasia independen lainnya, kami memperoleh kunci penandatanganan pribadi dari master_key dan menyebutnya signing_key. Aplikasi menggunakannya untuk menandatangani data dan kunci. Perangkat lain dan server menggunakan bagian publiknya untuk memeriksa tanda tangan tanpa membaca isinya.
Kami dapat memulihkan pasangan ini dari master_key yang sama. Aplikasi memperoleh nilai awal darinya: 32 byte yang secara unik menentukan kunci penandatanganan.
Untuk mendapatkan kunci berbeda untuk tugas berbeda, kami menyertakan label tetap dalam penghitungan: di sini, confidex/vault/ed25519/v1. master_key yang sama dan label yang sama selalu menghasilkan kunci yang sama.
Kata sandi utama belum memasukkan perhitungan ini. Kedua pasangan bergantung pada byte acak master_key, bukan pada string yang dibuat oleh seseorang. Pembedaan ini akan menjadi penting nantinya: kata sandi akan melindungi akses ke kunci yang sudah ada, sehingga kita dapat mengubahnya tanpa mengubah kunci itu sendiri.
Derivasinya menggunakan fungsi derivasi kunci berbasis hash: HKDF, HMAC-based Extract-and-Expand Key Derivation Function. Confidex menggunakannya dengan SHA-256, oleh karena itu dinamakan HKDF-SHA256. 32 byte yang dihasilkan adalah seed untuk pasangan Ed25519. Bagian publik dari signing_key tidak mengungkapkan secret_key, jadi kami dapat mengirimkannya ke server untuk verifikasi tanda tangan.
Apa itu file kunci dan apa fungsinya?
Buka pintu ruang pod, HAL.
Pengembaraan Luar Angkasa
Maaf, Dave. Saya khawatir saya tidak bisa melakukan itu.
Kunci menyimpan master_key terenkripsi. Membukanya memerlukan kata sandi utama dan secret_key. Header publik berisi versi format, pengidentifikasi vault, bagian publik signing_key, salt, dan parameter pemrosesan kata sandi. Versi format internal dienkripsi bersama master_key.
Saat membuat kunci, aplikasi sudah memiliki master_key, sehingga menghitung secret_key sebelum enkripsi. Saat membukanya, pengguna menyediakan secret_key secara terpisah: tidak ada dalam salinan kunci publik.
Setelah dekripsi, aplikasi menghitung bagian publik dari master_key menggunakan X25519 dan mengharuskannya untuk mencocokkan secret_key. Kemudian memulihkan signing_key dan membandingkan bagian publiknya dengan kunci di header. Dengan cara ini, kunci itu memeriksa dirinya sendiri.
Untuk memperlambat tebakan kata sandi utama, setiap upaya harus memakan waktu dan memori. Confidex menggunakan Argon2id dengan memori 64 MiB dan tiga pass. Pemrosesan kata sandi juga menggunakan garam acak. Garamnya bukan rahasia: itu membuat kata sandi yang sama menghasilkan hasil yang berbeda di kunci yang berbeda.
Jika secret_key sudah diketahui, panjang sandi akan mengubah skala serangan secara signifikan. Ada sekitar 12 juta kombinasi 5 huruf Latin kecil acak, sekitar 8 miliar dari 7, dan sekitar 95 kuadriliun dari 12. Beralih dari 7 menjadi 12 huruf meningkatkan ruang pencarian hampir 12 juta kali lipat. Jika pencarian 7 huruf memakan waktu rata-rata satu hari pada mesin tertentu, 5 huruf akan memakan waktu sekitar dua menit, dan 12 huruf membutuhkan waktu sekitar 33.000 tahun.
Untuk mendapatkan satu kunci dari hasil Argon2id dan secret_key, kita menggunakan fungsi HKDF-SHA256 yang diperkenalkan sebelumnya, dengan label confidex/lock/wrap/v1. Hasilnya adalah wrapping_key, kunci yang mengenkripsi gembok tersebut.
Kita harus menyembunyikan master_key dan melindunginya dari gangguan. XChaCha20-Poly1305 mengenkripsinya dengan wrapping_key dan menambahkan tag autentikasi. Tag mengikat ciphertext ke header publik: kata sandi yang salah atau perubahan pada byte yang dilindungi membuat pembukaan gagal. Tag dan nilai acak sekali pakai, nonce, disimpan bersama ciphertext.
Setiap kali kunci dibuat atau dienkripsi lagi, aplikasi memperoleh garam baru dan nonce. Konten terenkripsi mencakup satu byte versi dan 32 byte master_key; tag nonce 24-byte dan 16-byte disimpan di sampingnya. Dokumentasi algoritma menjelaskan konstruksinya.
Ketidakcocokan antara kunci yang dipulihkan dapat disebabkan oleh format kunci yang salah atau bug pada program yang membuatnya. Kerusakan byte acak biasanya terdeteksi lebih awal, saat tag dicentang.
Server tidak memiliki rahasia yang diperlukan untuk memeriksa tag ini. Untuk mentransfer kunci, aplikasi menambahkan tanda tangan yang dibuat dengan signing_key. Ini mencakup seluruh file yang sudah selesai, termasuk header publik dan konten terenkripsi. Server memeriksa tanda tangan dan, pada unggahan pertama, menyematkan seluruh bagian publik dari signing_key dan pengidentifikasi brankas internal. Ia memeriksa perubahan selanjutnya terhadap kunci ini. Kunci dan data terenkripsi ditandatangani dengan signing_key yang sama, dipulihkan dari master_key.
Lemari besi
Kami sekarang dapat mempublikasikan kuncinya. Hanya seseorang yang mengetahui secret_key dan master password yang dapat membaca master_key, sementara siapa pun dapat mengambil kunci penandatanganan publik dari file kunci dan memeriksa data yang ditandatangani tanpa melihat konten kunci atau datanya.
Misalkan kata sandinya adalah SuperPa55worD. Untuk saat ini kita hanya ingin menyimpannya: setelah aplikasi ditutup, kita harus dapat mengirim berkas kunci dan kata sandi terenkripsi ke mana saja tanpa ada yang melihat SuperPa55worD, sambil tetap memungkinkan mereka memverifikasi bahwa data itu berasal dari kita. Enkripsi harus dapat dibalik jika kita memiliki secret_key dan master password. Sandi simetris menggunakan kunci yang sama untuk mengenkripsi dan mendekripsi. Karena kunci itu tidak disimpan, kita harus dapat memulihkannya menggunakan berkas kunci. Kita menyebutnya message_key.
Kami akan mendapatkan message_key terpisah untuk setiap enkripsi baru. Kita masih perlu mengatur pemulihannya: membiarkan kunci terbuka di samping teks sandi akan membuat siapa pun yang memiliki salinannya dapat membaca semuanya.
Dari byte acak baru, aplikasi membuat pasangan X25519 sementara hanya untuk enkripsi ini: record_key. Kemudian mengambil bagian pribadi dari record_key dan kunci publik permanen brankas, secret_key, dan menghitung rahasia bersama. Fungsi terpisah memperoleh message_key dari rahasia itu; kunci ini mengenkripsi kata sandi kami.
Saat membaca, aplikasi mengambil rute lain. Ia menggunakan master_key dan bagian publik dari record_key. Kedua kunci ini menghasilkan rahasia bersama yang sama. Secara matematis, kedua rute mengalikan titik awal yang sama pada kurva dengan dua bilangan pribadi, dalam urutan berbeda. Urutannya tidak mengubah hasilnya. Oleh karena itu, kami dapat memulihkan kunci dekripsi tanpa menjaga bagian pribadi record_key.
Mendapatkan rahasia ini dari dua kunci publik secara praktis tidak mungkin: rute mana pun memerlukan kunci privat yang sesuai. Poin publik tidak memungkinkan kami memulihkan nomor pribadi yang menghasilkannya secara efisien. Trik matematis ini mendasari keamanan brankas. Tapi itu tidak menghentikan seseorang untuk memberi kami lemari besi lain yang kuncinya terpasang padanya.
Sekarang kita dapat menyimpan ciphertext di samping bagian publik dari record_key. Bagian pribadi dari record_key dan message_key yang digunakan tidak lagi diperlukan untuk pembacaan selanjutnya. Nantinya, aplikasi memulihkan kunci dekripsi dari master_key dan bagian publik yang disimpan dari record_key. Dengan demikian, satu rahasia brankas permanen dapat mendekripsi banyak nilai terenkripsi secara terpisah, masing-masing dengan message_key miliknya sendiri yang dipulihkan bila diperlukan.
Menyembunyikan teks saja tidak cukup: kita juga harus mendeteksi gangguan saat membacanya. Oleh karena itu enkripsi menambahkan tag otentikasi, nilai pendek yang diperiksa bersama dengan data terenkripsi. Jika pemeriksaan gagal, aplikasi menolak hasilnya. Itu tidak boleh menampilkan teks acak atau diubah sebagai kata sandi yang berhasil didekripsi.
Kami telah menjelaskan konstruksi di mana pasangan asimetris membantu membangun rahasia bersama, sementara message_key mengenkripsi kontennya. Ini disebut Hybrid Public Key Encryption, atau HPKE, dan ditentukan dalam RFC 9180. Confidex menggunakan X25519 untuk menyetujui rahasia, HKDF-SHA256 untuk mendapatkan kunci, dan ChaCha20-Poly1305 untuk enkripsi dengan pemeriksaan integritas. Di sini, HKDF mengambil rahasia bersama yang disetujui menggunakan record_key dan pasangan master_key; ini adalah pekerjaan terpisah dari menurunkan Ed25519 seed. RFC 5869 menjelaskan HKDF.
Untuk enkripsi berikutnya, aplikasi membuat konteks record_key baru dan HPKE baru. Penggunaan kembali kombinasi message_key dan nonce akan merusak perlindungan sandi. Di Confidex, kunci dan nonce diturunkan di dalam HPKE, digunakan untuk satu pesan, lalu konteksnya dibuang.
ChaCha20 sendiri menggunakan message_key 256-bit. Untuk memahami serangan langsung, bayangkan superkomputer fiksi yang menguji 10¹⁸ kandidat kunci per detik. Mencari 2²⁵⁶ kemungkinan kunci akan memakan waktu sekitar 1,8 × 10⁵¹ tahun. RFC 8439 menjelaskan algoritma dan analisisnya.
Sebuah rekor
Kami senang?
Fiksi Bubur
Kami telah menyimpan kata sandinya, tetapi untuk masuk ke suatu situs web biasanya juga memerlukan nama pengguna dan alamat. Terkadang kita membutuhkan catatan di sampingnya. Jika kita menyimpan nilai-nilai ini secara terpisah, aplikasi harus mengingat bahwa keduanya adalah milik bersama. Mari kita masukkan “kertas-kertas” ini ke dalam amplop. Kami menggabungkan bidang ke dalam satu koleksi dan menyebutnya sebagai record.
Catatan dapat berisi kombinasi bidang apa pun. Aplikasi ini merakitnya menjadi satu paket, mengompresnya, dan mengenkripsi keseluruhannya menggunakan konstruksi yang baru saja kami jelaskan. Nilai bidang, nama, tipe, dan tag semuanya dilindungi. Mengubah alamat situs web, meskipun sandi tetap sama, akan membuat versi baru dari seluruh data, dienkripsi dengan record_key baru.
Rantai hash
APAKAH PROYEK ANDA MENGGUNAKAN BLOCKCHAIN? → TIDAK
XKCD 2267
Seseorang yang mengetahui secret_key dapat menyiapkan catatan terenkripsi untuk brankas kami. Untuk menghindari penerimaannya sebagai milik kami, aplikasi menandatangani setiap versi rekaman dengan signing_key. Tanpa kunci ini, pihak luar tidak dapat membuat versi dengan tanda tangan yang akan diterima oleh server dan perangkat lain.
Versi terenkripsi memerlukan beberapa informasi yang menyertainya: vault dan rekaman mana yang dimilikinya, kapan diubah, dan versi mana yang mendahuluinya. Ini membentuk header publik. Ini tidak berisi kata sandi, tetapi masih memerlukan perlindungan: jika tidak, seseorang dapat mengubah kepemilikan dan hubungan objek tanpa menyentuh teks sandi. Aplikasi menandatangani byte header yang tepat bersama dengan ciphertext. Mengubah data yang ditandatangani akan membatalkan tanda tangan.
Servernya menyerupai gudang paket yang disegel. Ia melihat header, konten terenkripsi dan tanda tangan, tetapi tidak melihat kata sandi di dalamnya, dan ia mengetahui bahwa penerima ada di kota tersebut. Ia memeriksa tanda tangan terhadap bagian publik yang disematkan pada signing_key dan memutuskan apakah akan menerima versi baru. Ini tidak memerlukan kata sandi utama atau secret_key atau kunci pribadi apa pun. Saat membaca, klien memeriksa tanda tangan yang sama, lalu memeriksa kembali pengikatan antara konten dan header selama dekripsi, menggunakan tag autentikasi HPKE.
Tanda tangan juga melindungi hubungan antar versi. Hash SHA-256 dari byte header dan ciphertext yang tepat menjadi pengidentifikasi versi. Header versi berikutnya berisi pengenal versi sebelumnya. Saat kami mengubah kata sandi situs web, aplikasi membuat dan menandatangani objek baru dengan tautan ini. Benda lama dan tanda tangannya tetap tidak berubah. Oleh karena itu, mengganti pendahulunya sambil mempertahankan tanda tangan versi baru akan gagal. Jika dua perangkat membuat perubahan independen, kedua cabang akan bertahan; versi gabungan dapat merujuk pada keduanya.
Ingat blockchain? Itu ada :-).
Header publik memungkinkan kita memeriksa tautan tanpa mendekripsi, namun beberapa informasi tetap terlihat. Server mengetahui pengidentifikasi, tautan versi, waktu perubahan yang diberikan oleh klien, jumlah bidang, dan ukuran masing-masing nilai bidang yang dikodekan. Enkripsi menyembunyikan isi catatan, bukan informasi struktural ini.
Sinkronisasi
“Bukan kebocoran data, cadangan terdistribusi gratis.”
meme internet.
Untuk bekerja pada beberapa perangkat, kita perlu mentransfer riwayat kunci dan rekaman di antara perangkat tersebut. Format objek tidak berubah: kami mentransfer byte dan tanda tangan terenkripsi yang sama yang disimpan secara lokal.
Untuk transfer manual, aplikasi membuat arsip ZIP yang berisi kunci, semua versi rekaman terenkripsi yang disimpan secara lokal, dan daftar file yang ditandatangani. Baik sandi maupun secret_key tidak disertakan. Di perangkat lain, aplikasi pertama-tama membuka kunci, lalu memeriksa tanda tangan, mendekripsi catatan, dan memulihkan salinan brankas lokal. Impor ditujukan untuk perangkat yang belum ada brankas ini; itu tidak menggabungkan riwayat dengan salinan yang sudah ada.
Server sinkronisasi menukar perubahan antar copy pekerjaan. Pada unggahan pertama, ia menerima kunci bertanda tangan, memeriksa tanda tangan, dan menghitung alamat brankas publik dari bagian publik signing_key. Ia kemudian mengingat kunci ini dan pengidentifikasi brankas dari header: ini mendaftarkan brankas di server. Perubahan selanjutnya harus cocok dengan pengidentifikasi ini dan membawa tanda tangan dari kunci yang sama.
Perangkat baru memerlukan alamat vault dan secret_key, sehingga aplikasi menggabungkannya menjadi kode transfer dengan checksum. Pengguna mentransfer kode ini ke perangkat dan memasukkan kata sandi utama secara terpisah; hanya alamat publik yang dikirim ke server.
Setelah membuka kunci yang diunduh, aplikasi membandingkan bagian publik dari kunci yang dipulihkan dengan secret_key dan header, dan memeriksa alamat yang dihitung terhadap kode transfer. Kemudian menyimpan bagian publik terverifikasi dari signing_key untuk memeriksa catatan.
Selama sinkronisasi, aplikasi mengambil versi yang hilang dan mengunggah perubahannya. Server memeriksa tanda tangan dan link ke versi sebelumnya, menyimpan objek yang diterima dan meneruskannya ke perangkat lain. Memeriksa data yang diterima, mendekripsinya, dan menyelesaikan konflik semuanya terjadi di perangkat pengguna.
Pendaftaran tidak meminta nama, alamat email, atau kata sandi terpisah. Server melihat alamat publik, kunci verifikasi tanda tangan, header publik, ukuran objek, dan tautan versi. Itu juga melihat alamat IP dan waktu permintaan. Kata sandi utama, secret_key, kunci pribadi, dan konten rekaman teks biasa tetap ada di perangkat pengguna. Siapa pun yang mengetahui alamat publik dapat mengunduh objek terenkripsi, tetapi membacanya memerlukan rahasia pengguna.
Pemeriksaan ini mendeteksi objek palsu, namun tidak membuktikan bahwa server telah menunjukkan riwayat lengkap dan status terkininya. Itu dapat menyembunyikan cabang yang belum pernah dilihat klien atau mengembalikan kumpulan data lama yang konsisten. Tanda tangan mengonfirmasi keaslian suatu objek, bukan kelengkapan respons server. Pengguna mungkin melihat perubahan yang hilang di perangkat lain, namun riwayat lama yang konsisten akan lolos verifikasi hash-chain.
Mengubah kata sandi utama
Jadi, apakah Yang Mulia mau mengubah kata sandinya?
Peretas (1995)
Mengubah kata sandi utama hanya akan mengenkripsi ulang kuncinya; master_key di dalamnya tetap sama. Oleh karena itu, catatan, tanda tangannya, secret_key dan alamat brankasnya tetap tidak berubah. Kunci baru ditandatangani dengan signing_key yang sama.
Salinan kunci lama masih terbuka dengan sandi lama dan secret_key yang sama; mengubah kata sandi tidak mencabut kunci yang sudah terekspos.
Gambaran lengkap
Kunci melindungi master_key, message_key mengenkripsi data, dan signing_key membuat tanda tangan. ZIP arsip dan server menerima objek terenkripsi dengan tanda tangan.