21 poin oleh GN⁺ 2025-09-19 | 1 komentar | Bagikan ke WhatsApp
  • UUIDv47 memungkinkan penyimpanan UUIDv7 yang dapat diurutkan di database, sambil menyediakan nilai yang tampak seperti UUIDv4 pada API eksternal
  • Hanya field timestamp yang di-mask dengan XOR untuk melindungi informasi waktu pada UUIDv7, sementara field acak lainnya tetap dipertahankan
  • Dengan masking menggunakan kunci 128-bit berbasis SipHash-2-4, perlindungan informasi yang aman dimungkinkan tanpa risiko paparan kunci
  • Proses encode/decode deterministik dan reversibel, serta keacakan tetap terjaga sehingga risiko tabrakan rendah
  • Hasil benchmark menunjukkan performa yang sangat cepat dan metode integrasi yang sederhana, serta mudah terhubung dengan database seperti PostgreSQL

Gambaran proyek dan signifikansinya

  • UUIDv47 adalah pustaka C open-source yang menyimpan UUIDv7 yang unggul untuk pengurutan dan pengindeksan di dalam database, sambil mengekspos nilai yang tampak seperti UUIDv4 ke API dan sistem eksternal, sehingga mencapai perlindungan privasi dan pemrosesan berperforma tinggi secara bersamaan
  • Dibanding algoritme konversi UUID lain, proyek ini memiliki keunggulan pembeda dalam hal pemetaan reversibel, kompatibilitas RFC, keamanan dengan kunci yang tidak dapat dipulihkan, zero-deps, serta struktur sederhana yang hanya memerlukan file header

Fitur utama

  • Header-only C (C89), dapat diintegrasikan dengan mudah tanpa dependensi eksternal
  • Hanya field timestamp pada UUIDv7 yang di-mask dengan XOR untuk mencegah kebocoran informasi waktu, sementara field acak lainnya tidak diubah
  • Masking dengan keyed SipHash-2-4 memungkinkan perlindungan informasi yang aman menggunakan kunci 128-bit
  • Proses encode/decode bersifat deterministik dan sepenuhnya reversibel (dapat dipulihkan persis ke bentuk semula)
  • Mendukung pemetaan cepat antara UUID untuk penyimpanan database (v7) dan untuk paparan eksternal (v4)
  • Menyediakan contoh yang kaya seperti kode pengujian dan alat benchmark

Tujuan penggunaan dan manfaat

  • Dapat memanfaatkan UUIDv7 yang dapat diurutkan untuk memaksimalkan locality indeks dan efisiensi paging di dalam DB
  • Ke luar hanya mengekspos pola yang tampak seperti UUIDv4, sehingga mencegah kebocoran timestamp dan pelacakan
  • Menggunakan SipHash sehingga kunci tidak dapat dipulihkan dan keamanan secret key terjamin
  • Penanganan bit versi/varian yang kompatibel dengan RFC
  • Kecepatan operasi tinggi sehingga efisien bahkan di pemrosesan real-time dan lingkungan generasi massal

Struktur utama dan prinsip kerja internal

Layout UUIDv7

  • ts_ms_be: timestamp big-endian 48-bit
  • ver: high nibble pada byte ke-6 (0x7=DB, 0x4=eksternal)
  • rand_a: nilai acak 12-bit
  • var: varian RFC (0b10)
  • rand_b: nilai acak 62-bit

Logika masking dan pemetaan (Façade mapping)

  • Encoding: ts48 XOR mask48(R), set version=4
  • Decoding: encTS XOR mask48(R), set version=7
  • Field acak tidak diubah
  • Menggunakan field acak 10 byte sebagai input SipHash
  • Masking XOR dapat langsung dibalik jika mengetahui kuncinya

Model keamanan

  • Tujuan: kunci tidak boleh terungkap bahkan bila input diberikan secara selektif
  • Implementasi: menggunakan SipHash-2-4, sebuah fungsi pseudorandom terkeyed (PRF)
  • Memanfaatkan kunci 128-bit, dan disarankan melakukan derivasi kunci melalui HKDF dan sejenisnya
  • Saat rotasi kunci, disarankan tidak menyimpannya di dalam UUID, melainkan mempertahankan key ID kecil secara terpisah

API publik (C)

  • uuidv47_encode_v4facade : konversi v7→v4
  • uuidv47_decode_v4facade : pemulihan v4→v7
  • Juga menyediakan fungsi terkait pengaturan versi, parsing, dan formatting

Performa dan benchmark

  • Pada operasi masking SipHash (10B), kinerjanya di bawah 14ns/op, dan round trip penuh encode+decode berada di kisaran 33ns/op (berdasarkan Apple M1)
  • Menjamin pemrosesan cepat bahkan saat generasi dan pemetaan UUID dalam jumlah besar
  • Performa optimal pada opsi -O3 -march=native

Integrasi dan perluasan

  • Disarankan menangani encode/decode di batas API
  • Untuk integrasi PostgreSQL, tulis ekstensi C
  • Saat melakukan sharding, façade v4 dapat di-hash dengan xxh3, SipHash, dan sebagainya

Lain-lain

  • Tersedia port ke bahasa lain: Go (n2p5/uuid47) dan lainnya
  • Rekomendasi hash: xxHash bukan PRF sehingga berisiko membocorkan informasi; disarankan menggunakan SipHash

Lisensi

  • Lisensi MIT (Stateless Limited, 2025)

1 komentar

 
GN⁺ 2025-09-19
Komentar Hacker News
  • Halo, saya penulis uuidv47. Ide dasarnya adalah menggunakan UUIDv7 secara internal agar pengindeksan dan pengurutan di database tetap baik, tetapi menampilkan nilai yang terlihat seperti UUIDv4 ke luar agar pola waktu tidak terekspos ke klien.
    Cara kerjanya adalah melakukan masking XOR pada timestamp 48-bit dengan stream SipHash-2-4 yang diturunkan dari field acak UUID.
    Bit acaknya tetap dipertahankan, versinya berubah dari 7 di internal menjadi 4 di eksternal, dan nilai varian RFC juga tetap dipertahankan.
    Pemetaan ini bersifat injective: strukturnya adalah (ts, rand) → (encTS, rand).
    Dekodenya menggunakan skema encTS ⊕ mask sehingga round-trip conversion dapat dilakukan secara sempurna.
    Dari sisi keamanan, karena SipHash adalah PRF, meskipun orang luar melihat nilai yang sudah dipaketkan, kuncinya tidak akan bocor.
    Jika kuncinya salah, timestamp-nya juga akan menjadi sepenuhnya berbeda.
    Rotasi kunci juga bisa didukung dengan pengelolaan key-ID di luar.
    Kinerjanya hanya sekitar satu kali SipHash untuk 10 byte ditambah beberapa operasi load/store 48-bit, jadi overhead-nya ada di tingkat nanodetik; header-only C11, tanpa dependensi eksternal, dan tidak perlu alokasi.
    Pengujiannya mencakup reference vector SipHash, encode/decode round-trip, serta pengujian invariansi versi/varian.
    Saya penasaran dengan masukan kalian.

    • Saya suka idenya.
      UUID sering kali dibuat di sisi klien, dan dengan pendekatan ini sepertinya itu tidak memungkinkan.
      Kalau klien mengirim UUID buatannya lalu server mengembalikan versi yang sudah dimasking, bukankah seseorang bisa memberi dua UUID dengan ts berbeda tetapi rand sama sehingga menimbulkan celah?
      Jadi saya penasaran apakah pendekatan ini memang hanya cocok ketika UUIDv7 dibuat langsung oleh sistem itu sendiri.

    • Saya punya dua pendapat.

      1. Dengan cara ini, nilai tambah UUIDv7 jadi tidak bisa lagi dimanfaatkan pihak lain, jadi dari sudut pandang pengguna API itu agak disayangkan.
      2. Kalau API eksternal dan format penyimpanan internal berbeda, maka harus selalu melewati proses konversi ini, jadi pengelolaannya sedikit lebih rumit.
        Saya tidak yakin kerumitan tambahan itu sebanding dengan manfaatnya.
    • Kekhawatiran terbesar saya adalah kualitas entropi dari bit acaknya.
      UUIDv7 lebih berfokus pada pencegahan tabrakan, jadi penekanannya ada pada probabilitas collision, bukan pada ketakterdugaan.
      Karena itu, menurut RFC, soal non-kerandoman hanya dinyatakan sebagai anjuran (should), bukan keharusan (must), dan ada implementasi yang memakai PRNG lemah atau counter, bahkan ada yang memasukkan data jam tambahan ke tempat bit acak (lihat: RFC9562 s6.2 & s6.9).
      Jadi, memakai rand_a dan rand_b dari v7 langsung sebagai seed PRF bisa lebih berisiko dari yang terlihat jika datanya datang dari luar batas kepercayaan.
      Bahkan uuidv7() baru di PostgreSQL 18 mengisi seluruh rand_a dari timestamp presisi tinggi, dan itu pun tidak melanggar RFC.
      Jika melihat UUID yang dihasilkan dalam import massal, pendekatan v7-to-v4 ini pada akhirnya juga bisa dikelompokkan sehingga informasi tetap bisa bocor.
      Untuk pengumpulan data komponen mesin mungkin tidak masalah, tetapi untuk data identitas yang langsung terkait dengan manusia, perlu hati-hati.
      Pada akhirnya, kecuali Anda sendiri menjamin entropi yang benar-benar tepercaya, skema ini juga bisa membocorkan informasi tentang waktu, serial, atau korelasi, jadi sumber implementasi v7 wajib diperiksa langsung.

    • Menurut saya ini ide yang kurang bagus.
      Di PostgreSQL 18, parameter opsional shift menggeser timestamp sebesar interval yang diberikan.
      https://www.postgresql.org/docs/18/functions-uuid.html

  • Beberapa tahun lalu saya membuat skema sendiri dengan memakai ID numerik yang meningkat secara berurutan di DB, lalu mengekspos string acak pendek sepanjang 4–20 karakter ke luar.
    Saat itu saya memakai instance kustom dari keluarga sandi Speck, dan menurut saya hasilnya cukup solid dan lumayan meyakinkan.
    Sudah selesai dibuat, tetapi karena proyek yang akan memakainya saya tunda, saya tidak pernah merilisnya.
    Saya berencana mempublikasikan materi itu secara resmi tahun ini atau tahun depan.
    Saya juga punya catatan yang merangkum cara implementasi serta kelebihan dan kekurangannya, kalau tertarik silakan lihat.
    https://temp.chrismorgan.info/2025-09-17-tesid/

    • Saya juga pernah mencoba mengaburkan bigserial PKID dengan Speck, tetapi implementasi lintas platform-nya kurang, dan khususnya dukungan di pgcrypto lemah, jadi saya memilih base58(AES_K1(id{8} || HMAC_K2(id{8})[0..7])).
      Hasilnya memang lebih panjang, biasanya sekitar 22 karakter, tetapi bisa diimplementasikan di hampir semua lingkungan dan performanya juga sangat memadai.

    • Ide bagus.
      Dengan konsep yang mirip, sqids (nama sebelumnya: hashids) juga layak dilihat.
      https://sqids.org/

  • Dulu saya pernah menangani hal serupa dengan dua kolom: uuid untuk publik dan bigint PK yang tidak diekspos ke API (ini jauh sebelum uuidv7 ada).
    Dari sisi kenyamanan memang kalah dibanding uuid, tetapi kalau PK-nya bisa dipisahkan dengan baik, keuntungan besarnya adalah dump dari database yang berbeda bisa digabungkan dengan mudah.
    Bahkan jika pencarian dilakukan berbasis hash, rasanya tetap perlu dua kolom, tapi mungkin saya saja yang salah memahami cara kerja hash ini.

    • Konversinya bisa dibalik dengan kunci kriptografis rahasia.
      Nilai uuidv4 dari request bisa diubah kembali menjadi uuidv7 di database.
  • Idenya sendiri menarik, tetapi saya berharap database mendukung pemrosesan seperti ini secara langsung.
    Artinya, UUIDv7 bisa saling dikonversi dengan “UUIDv4”, dan dalam query pun kedua format itu bisa dibedakan serta dipakai secara eksplisit.

  • Proyek yang sangat keren.
    Saya sempat membuat implementasi Go dengan memanfaatkan library siphash milik dchest.
    https://github.com/n2p5/uuid47
    Referensi: https://github.com/dchest/siphash

  • Proyeknya menarik, tapi saya penasaran apakah ada contoh nyata yang menunjukkan bagaimana bagian waktu pada uuid v7 bisa berisiko jika terekspos.

    • Kalau pola atau urutan perilaku pengguna terekspos, itu bisa menempatkan mereka dalam situasi yang tidak nyaman.

      • “Mantan suamimu: melihat userID-mu di situs kencan itu, jelas kamu bikin akun saat pesta Tom, kan?”
      • “Kamu bilang TZ-mu XYZ, tapi log imageID-mu (unik berdasarkan waktu pembuatan) kok selalu tercatat jam 3 pagi?”
        Ini mungkin tidak relevan untuk pesan individual atau transaksi real-time, tetapi untuk pembuatan akun pengguna atau data jangka panjang, orang bisa menyalahgunakannya untuk melacak identitas.
    • Dulu di CTF saya pernah brute force sebagian UUID yang dipakai sebagai kunci AES.
      Karena kuncinya sebagian diturunkan dari sumber waktu, serangan jadi mungkin dilakukan jika waktu sistem saat kunci dibuat dapat diketahui.
      Contoh sederhana lainnya adalah layanan berbagi file yang hanya mengekspos struktur seperti website.com/GUID, tanpa secara terpisah mengumumkan waktu upload file.
      Jika memakai UUIDv7, waktu unggah file bisa diperkirakan dari UUID itu sendiri.
      Ini mungkin belum tentu ancaman keamanan besar, tetapi tetap merupakan kebocoran informasi yang tidak disengaja.

    • Misalnya bayangkan sistem yang menyimpan data medis.
      Untuk analisis, hasil MRI diunggah segera setelah pemindaian, dan meskipun data pribadi dihapus,
      timestamp pada uuidv7 tetap memungkinkan analisis korelasi dari luar, sehingga orang bisa menyimpulkan, “Pada tanggal itu hanya ada satu orang yang menjalani MRI, jadi ini pasti MRI milik orang tersebut.”

  • Hal paling merepotkan dari uuidv7 adalah sangat sulit membandingkannya dengan mata telanjang di daftar (diff).
    Kalau di psql ada layer visualisasi yang memindahkan bit acak ke depan tetapi tetap menjaga urutan berdasarkan timestamp, UX-nya akan jauh lebih baik.

    • Saya pribadi membiasakan diri untuk hanya melihat bagian akhir UUID.

    • Tinggal buat fungsi sendiri lalu pakai dalam query.
      Misalnya, setelah representasi hex, balik string-nya, atau tampilkan sebagai reversed base64 agar lebih pendek dan lebih mudah dibedakan.

  • Pendekatan ini terlihat sangat masuk akal.
    Namun menurut saya, kepanikan soal timestamp yang terekspos, atau klaim bahwa tereksposnya sequential ID langsung berarti terbukanya vektor serangan dan informasi bisnis, lebih mirip kekhawatiran berlebihan daripada masalah keamanan yang nyata.
    Cukup tambahkan bilangan acak besar ke nilai int secara berkala; sifat monotonic-nya tetap terjaga, tetapi pengamat dari luar jadi sulit menangkap polanya.
    Pada akhirnya, menurut saya ada kecenderungan melebih-lebihkan sambil berpura-pura khawatir soal kebocoran informasi penting.

    • Yang terekspos di sini bukan informasi bisnis, melainkan informasi klien.
      Informasi yang dibocorkan sistem itu sendiri mungkin tampak tidak terlalu berarti, tetapi jika diamati dalam jumlah besar atau sebagai deret waktu, data tambahan bisa diinferensikan.
      Sebagai contoh, seperti ceramah SpiegelMining dari David Kriesel, hanya dengan mengumpulkan tanggal artikel koran dan nama penulis, pola tentang siapa yang sedang liburan kapan bisa ditarik.
      Jika data banyak penulis dibandingkan, hubungan asmara di dalam kantor pun bisa ikut terungkap.
  • Saya penasaran kenapa tidak memakai kunci kripto yang berbeda untuk setiap sesi, lalu hanya mengekspos id yang sudah dienkripsi ke luar.
    Dengan begitu DB cukup memakai sequential id biasa saja, bukan?

    • Untuk mendekripsi bit timestamp yang disembunyikan di dalam token, Anda harus tahu kunci mana yang dipakai.
      Kalau kuncinya diganti secara berkala, pengelolaan kuncinya jadi sangat rumit, dan bagaimana menentukan kunci yang tepat setiap saat pun menjadi masalah.
  • Saya penasaran kenapa tidak memakai versi 8 alih-alih versi 4.
    v4 berarti bit acak, padahal kenyataannya tidak benar-benar acak.
    v8 tidak membatasi makna bit-bit tersebut.

    • Saya juga tidak tahu jawaban pastinya, tetapi jika entropinya tinggi, mungkin bisa dianggap mirip PRNG berbasis seed.
      Karena tujuan pendekatan ini memang agar terlihat acak dari luar, bisa jadi justru v8 akan lebih mencolok.