Situs ini dikemudikan dua tombol, seperti mesin pemiliknya. Membaca: j dan k menggulir, d dan u setengah layar, gg ke atas, G ke bawah, H dan L pindah jendela, ? membuka panduan. Spasi adalah kunci leader Neovim dan mengurus isi: Spasi lalu h beranda, r riset, p proyek, g gear, a tentang, / tag, atau Spasi lalu angka untuk melompat ke jendela bernomor itu. Home adalah prefix tmux dan mengurus jendela: Home lalu c membuka terminal, Home lalu & menutup jendela, Home lalu spasi ke jendela berikutnya. Saat fokus ada di papan ketik, h j k l memindahkan pilihan antar tombol dan Enter membukanya.

RisetR TentangA Gear — G untuk gear — papan ketik, terminal, editor, homelab.G
    ProyekP Tag — / seperti di vim: cari./
    ×
    Menu

    sirna: Hancurkan Kuncinya, Bukan Salinannya

    Penerus otm. otm menyimpan kunci induk di server, jadi server bisa mendekripsi semua yang dipegangnya — dan itu tidak bisa ditambal, karena begitulah bentuk rancangannya. sirna tidak pernah membiarkan kuncinya sampai ke server.

    sirnahilang tanpa meninggalkan jejak.

    Ini ada karena satu batas di otm yang tidak bisa dijangkau tambalan apa pun. otm menyimpan SECRET_KEY induk di server dan memakainya untuk mengenkripsi tiap kunci pesan. Itu tidak apa-apa untuk otm — peragaan kriptografi terapan yang memang jalan, dan tetap begitu — tapi artinya server bisa mendekripsi semua yang dipegangnya. Untuk alat yang seluruh janjinya ada pada kata rahasia, itu bukan bug yang perlu diperbaiki. Itu bentuk rancangannya.

    Jadi janjinya yang diganti.

    Janjinya “hancurkan kuncinya”, bukan “hancurkan salinannya”

    Kamu tidak bisa menghapus data dari perangkat orang lain. Begitu penerima memegang bita-nya, bita itu miliknya — disimpan, dipotret layar, dicadangkan ke awan yang tidak pernah kamu dengar namanya.

    Yang BISA kamu hapus adalah kunci. Hancurkan ia, dan semua salinan sandi-teksnya mati pada saat yang sama, termasuk salinan yang tidak kamu kendalikan dan tidak pernah kamu tahu ada.

    Itu janji yang lebih sempit daripada “pesannya hilang”, dan cuma itu yang benar-benar bisa ditepati.

    Dua saluran, dan satu saja tidak pernah cukup

    Amplopnya boleh lewat mana saja — WhatsApp, surel, bucket S3. Kuncinya lewat jalan terpisah: 24 kata, atau kode QR, ditampilkan tepat sekali.

    Kunci itu tidak pernah menyentuh server, dan tidak pernah menumpang di URL. Bocor satu saluran tidak cukup.

    Yang dipegang server hanya amplopnya. crates/server menaut crates/core untuk pengurai header saja, jadi “server tidak bisa mendekripsi” adalah sifat hasil build, bukan kalimat di README.

    Yang bisa diketahui orang asing dari amplopnya

    $ sirna inspect report.pdf.sirna
    format version : 1
    chunk size     : 65536 bytes
    kind           : file
    envelope size  : 104883299 bytes

    Cuma itu. Nama berkas, tipe MIME, panjang sebenarnya, kedaluwarsa, dan catatan apa pun ada di dalam blok metadata yang terenkripsi.

    Data terautentikasi tiap potongan melipat masuk hash BLAKE3 dari header, sehingga header ikut terautentikasi tanpa MAC terpisah. Batas potongan diambil dari panjang plainteks yang terautentikasi, bukan ditebak dari sisa bita — dan itulah kenapa unduhan yang terpotong melaporkan terpotong, bukan kunci salah.

    crates/core menerima jam dan RNG sebagai parameter. Itu bukan kerewelan: wasm32 tidak punya SystemTime, dan vektor uji yang persis bita mustahil tanpa RNG yang bisa disemai. Dua puluh dua vektor itu satu-satunya yang menahan empat klien — CLI, peramban, Android, server — dari diam-diam saling menyimpang.

    Sekali baca berarti sekali, dan ongkosnya ditulis

    Versi pertama mengembalikan klaimnya ketika pengiriman gagal dan memberi tiga percobaan. Alasannya baik hati: pembaca dengan sambungan naik-turun tidak seharusnya pulang dengan tangan kosong.

    Itu pertukaran yang salah. Artinya satu blob bisa disajikan lebih dari sekali, dan sekali itu seluruh janjinya. Jaminan yang berlaku biasanya bukan jaminan yang bisa dinalar siapa pun, dan itu lebih buruk daripada aturan keras yang ongkosnya diketahui.

    Jalur coba-lagi dibuang. Sebuah blob berpindah langsung dari hidup ke terpakai di dalam UPDATE yang mengklaimnya, tanpa keadaan antara dan tanpa jalan kembali. Ia habis begitu diklaim — tidak peduli ada galat penyimpanan, sambungan putus, atau tab ditutup. Tidak ada yang dapat percobaan kedua, termasuk pembaca yang tidak pernah menerimanya.

    Ongkosnya nyata, jadi dinyatakan alih-alih dihaluskan: pengiriman yang gagal menghancurkan pesannya, dan pengirim harus mengirim yang baru. Runbook-nya menulis bahwa “tadi error dan sekarang hilang” memang harus diharapkan, dan itu perilaku yang benar. Halaman bacanya menyatakan pesannya habis begitu Buka ditekan. Tidak ada yang pantas mempelajari itu dari utas dukungan.

    Membuang keadaan antara itu ikut menghapus cabang reaper untuk blob yang tertinggal di tengah unduhan, penghitung percobaan, dan dua metode. Mesin keadaannya jadi dua keadaan dan satu transisi — cukup kecil untuk diaudit sekali lihat, dan itu ukuran yang tepat untuk satu aturan yang jadi nama produknya.

    Kunci yang benar dilaporkan sebagai salah ketik

    Menempel 24 kata itu gagal dengan “kunci itu kurang tepat — periksa salah ketik”. Itu hal terburuk yang bisa dikatakan kepada orang yang kuncinya justru sempurna.

    Kliennya menebak mode sebuah amplop dengan memeriksa apa yang diketik pengguna: ada spasi berarti frasa, tidak ada spasi berarti passphrase. Menyalin kata-kata dari daftar bernomor di layar tersegel memisahkannya dengan baris baru, jadi tebakannya meleset dan jalur passphrase menolak frasa yang memang bukan passphrase.

    Amplopnya menyatakan modenya sendiri, dan inspect sudah melaporkannya. Jawaban itu dibuang demi sebuah tebakan. Sekarang tidak ada yang menebak.

    Pengurai mnemonic-nya juga menuntut spasi tunggal. Kata-kata datang dipisah baris baru dari sebuah daftar, dipisah spasi ganda lewat aplikasi chat, dan dengan tab dari terminal — semuanya frasa yang sama bagi orang yang memegangnya. Spasi diciutkan sebelum diurai, dengan uji untuk tiap bentuk termasuk CRLF.

    Ini alasan kenapa taksonomi galatnya sepadan dengan repotnya. Laporannya “periksa salah ketik”, dan karena itu memetakan ke kode tersendiri alih-alih kegagalan umum, ia menunjuk langsung ke penguraian kunci — bukan ke dekripsi, tempat bug-nya tidak berada.

    wrong key, or the envelope has been altered5
    envelope is incomplete — data is missing from the end6
    this message has expired9
    key checksum does not match — likely a typo12

    Kodenya sama persis di semua klien. Orang dengan unduhan rusak tidak dikirim berburu kuncinya.

    Fallback yang menyembunyikan halaman rusak

    Membuka tautan pesan menampilkan header, footer, dan tidak ada apa-apa lagi — mati, tanpa gaya, dan tampak seperti deploy yang belum selesai.

    Markup-nya menunjuk ./app.js dan ./style.css. Aplikasinya disajikan dari dua bentuk URL, / dan /m/<id>, jadi keduanya menyelesaikan ke /app.js di satu sisi dan /m/<id>/app.js di sisi lain. Rute dalam — yang justru selalu dibuka tiap penerima — meminta berkas yang tidak ada di situ.

    Kenapa ia bisa lolos adalah bagian yang lebih mengajarkan. Fallback-nya menjawab tiap jalur tak dikenal dengan index.html dan status 200, jadi aset yang hilang kembali sebagai halaman HTML yang sempurna berhasil. Peramban baru sadar ketika HTML itu gagal diurai sebagai JavaScript. Rujukan yang rusak tidak bisa dibedakan dari yang benar pada tingkat HTTP, dan persis begitu ia lolos dari pemeriksaan saya sendiri: saya memastikan / dan /m/<id> mengembalikan 200, tidak pernah memastikan aset yang disebut halaman itu bisa dijangkau dari tempat peramban memintanya.

    Apa pun yang berbentuk berkas sekarang 404, dan hanya jalur tanpa ekstensi yang jatuh ke aplikasi.

    Mode custody, dan lima digit yang dibacakan

    Menyerahkan kata berarti pengirim tidak bisa menarik kuncinya kembali. Mode custody menahan kuncinya di perangkat pemilik dan melepasnya ke satu pembaca tertentu pada saat baca, setelah itu pemiliknya menghancurkannya. Itu yang membuat pencabutan jadi nyata, bukan sekadar retorika.

    Pelepasannya menurunkan kunci pembungkus dengan BLAKE3 atas rahasia bersama berikut kedua kunci publik dan id blob-nya. Mengikat kedua identitas dan pesannya ke dalam penurunan itu membuat relay yang menukar salah satu sisi menghasilkan sesuatu yang tidak bisa didekripsi — bukan sesuatu yang diam-diam tetap bekerja.

    Short authentication string-nya lima digit atas kedua kunci publik dan id blob. Lima adalah kompromi yang disengaja: satu berbanding seratus ribu terhadap penyerang yang dapat satu kesempatan, dan cukup pendek supaya manusia sungguh mau membacakannya. Lebih panjang akan memperbaiki aritmetikanya dan memperburuk hasilnya, karena orang melewati apa yang merepotkan.

    Uji pelepasannya ditulis dari sudut pandang relay, bukan jalur bahagia, karena relay adalah lawan yang diasumsikan rancangan ini. Ia melihat tiap frame. Ia bisa mengganti pembacanya — dan justru itu yang mengubah digit di kedua layar. Ia tidak bisa membaca kuncinya, memutar ulang sebuah grant ke pesan lain, mengubah satu bita tanpa ketahuan, atau memalsukan tanda terima.

    Sampai di mana

    Hidup di sirna.arisjirat.com. Spesifikasi berikut 22 vektornya, core, CLI-nya, server blob di atas Garage, klien peramban, dan bindings Kotlin sudah selesai — 22/22 vektor lolos juga di JVM. Aplikasi Android dengan penghancuran bersandar Keystore belum dibangun.

    62 uji. core memakai #![forbid(unsafe_code)].

    id en
    rss gh in