- Ini adalah eksperimen untuk membuat langsung dua bilangan prima sekitar 1024-bit yang dibutuhkan untuk kunci RSA 2048-bit, dengan implementasi dari pembangkitan bilangan acak hingga operasi bilangan bulat besar langsung di Rust tanpa dependensi eksternal
- Trial division sederhana cukup memadai pada 16-bit dengan waktu sekitar 40ms, tetapi bahkan pada 64-bit masih memerlukan 6,4 detik setelah optimasi, sehingga sulit diperluas ke 1024-bit
- Uji Fermat cepat, tetapi bisa gagal menyaring pseudoprime, sehingga penentuan akhir memakai uji Miller-Rabin dengan k=10
- Untuk melampaui batas tipe integer bawaan, dibuat BigInt sendiri, lalu waktu pembuatan bilangan prima 1024-bit turun dari sekitar 32 menit menjadi 60~90 detik saat struktur diubah dari array bool ke array byte lalu ke chunk
u64 - Implementasi akhir menggabungkan BigInt chunk
u64, pembagian cepat, trial division dengan bilangan prima kecil, peningkatan kandidat dengan+2, dan eksekusi paralel 16 thread, sehingga dapat menemukan bilangan prima 1024-bit rata-rata dalam sekitar 40ms, tetapi ini bukan pustaka kriptografi yang telah tervalidasi
Membuat sendiri bilangan prima 1024-bit untuk RSA
- Tujuannya adalah membuat sendiri bilangan prima yang bisa dipakai untuk pembuatan kunci RSA
- Kunci RSA 2048-bit dibuat dari hasil kali dua bilangan prima, jadi masing-masing membutuhkan bilangan prima berukuran sekitar 1024-bit
- Tantangannya pun secara alami menyempit menjadi pembuatan bilangan prima 1024-bit
- Eksperimen ini diberi tiga batasan
- Kode ditulis dari nol dan tidak memakai dependensi eksternal
- Menggunakan laptop dengan CPU AMD Ryzen 7 dan RAM 16GB tanpa perangkat keras eksternal atau cloud
- Bilangan prima harus dihasilkan dalam “waktu yang masuk akal”
- Bahasa yang dipilih adalah Rust yang baru dipelajari belakangan ini
- Bahasa ini dianggap cukup dekat ke konsep tingkat rendah, sekaligus cukup tingkat tinggi untuk memahami potongan kode
Batas trial division yang terlihat pada 16-bit dan 64-bit
- Alur dasarnya adalah membuat bilangan acak N-bit berulang kali, lalu berhenti jika lolos uji primalitas
- Bilangan acak dibuat dengan membaca langsung
/dev/urandomdi Linux alih-alih memakai craterandmilik Rust/dev/urandomadalah berkas perangkat semu yang memberi akses ke CSPRNG milik kernel Linux- Kernel mengumpulkan entropi dari lingkungan pengguna dan melakukan seed berkala pada stream cipher deterministik berbasis ChaCha20
- Untuk bilangan acak 16-bit, bit pertama dan bit terakhir diatur menjadi
1- Bit terakhir
1dipakai untuk memastikan angkanya ganjil - Bit pertama
1dipakai agar seluruh rentang bit yang dibutuhkan benar-benar terpakai
- Bit terakhir
- Pada 16-bit, hanya dengan trial division membagi dari
3sampaisqrt(num)pun sudah bisa menemukan bilangan prima dalam sekitar 40ms- Contoh eksekusi menghasilkan
Prime found: 44809, dan total waktunya sekitar 0,038 detik
- Contoh eksekusi menghasilkan
- Saat diperluas ke 64-bit, trial division sederhana memerlukan sekitar 30 detik
- Setelah itu diperbaiki dengan hanya memeriksa kandidat berbentuk
6k±1dan lebih dulu membagi dengan daftar bilangan prima kecil - Setelah perbaikan, waktu pembuatan bilangan prima 64-bit menjadi sekitar 6,414 detik
- Setelah itu diperbaiki dengan hanya memeriksa kandidat berbentuk
- Karena pada 64-bit saja sudah butuh 6 detik, menjadi jelas bahwa pendekatan ini sulit mencapai pembuatan bilangan prima 1024-bit
Beralih ke uji primalitas probabilistik
- Algoritme deterministik seperti APR-CL dan ECPP sempat dipelajari, tetapi secara matematis rumit dan sulit dijadikan target implementasi karena minim penjelasan yang mudah diakses
- Setelah melihat kode sumber OpenSSL dan rekomendasi NIST, dipastikan bahwa uji primalitas probabilistik memang luas dipakai dalam penggunaan nyata termasuk RSA
- Setelah itu, algoritmenya berubah dari “membuktikan bahwa bilangan itu prima” menjadi menilai bahwa bilangan tersebut adalah probable prime dengan tingkat akurasi tertentu
-
Uji Fermat
- Teorema kecil Fermat memakai relasi bahwa jika
pprima danatidak habis dibagip, makaa^(p-1) = 1 mod p - Karena perpangkatan biasa bisa menyebabkan overflow pada
u128, dibuatlah modular exponentiation pow()menerima eksponen sebagaiu32, dan menaikkanu128ke eksponen yang lebih besar bisa menimbulkan overflow- Perkaliannya sendiri juga bisa melampaui rentang
u128, sehingga sementara dipakai pendekatan menyimpan bilangan 64-bit di dalamu128 - Uji Fermat cepat, tetapi karena adanya Fermat pseudoprime, bilangan komposit bisa salah dinilai sebagai prima
- Bilangan komposit seperti ini memang jarang, tetapi tetap cukup banyak sehingga uji Fermat saja dianggap kurang andal
- Teorema kecil Fermat memakai relasi bahwa jika
-
Uji Miller-Rabin
- Miller-Rabin didasarkan pada prinsip yang sama dengan uji Fermat, tetapi digunakan sebagai algoritme uji primalitas probabilistik yang lebih kuat
- Implementasinya memisahkan pangkat dua dalam bentuk
n-1 = 2^s × d, lalu memeriksa beberapa kondisi a^d = 1 mod n- atau untuk suatu
0 <= r < s,a^(2^r × d) = n - 1 mod n - Dalam eksperimen 128-bit, bilangan prima ditemukan dalam sekitar 0,042 detik, mirip dengan uji Fermat
- Batas galat terburuk Miller-Rabin adalah
4^-k, dan untuknbesar rata-ratanya berada di kisaran8^-k - Saat
k=10, perhitungan peluang galat rata-ratanya adalah0.000000000931323% - Ini dibandingkan dengan peluang mendapatkan sisi kepala pada 30 lemparan koin berturut-turut, yaitu
2^-30 - Untuk kriptografi nyata, pemilihan base acak dan kondisi adversarial harus diperlakukan dengan lebih hati-hati
Membuat BigInt sendiri
- Dengan tipe integer bawaan Rust saja, sulit menangani bilangan yang cukup besar di atas 64-bit, sehingga perlu implementasi integer presisi arbitrer (BigInt)
- Karena ada batasan tidak memakai crate bigint eksternal, BigInt juga diimplementasikan sendiri
-
Percobaan 1: array digit angka
- Awalnya dicoba cara menyimpan bilangan besar sebagai array digit desimal
- Penjumlahan dan perkalian bisa diimplementasikan seperti hitung manual, tetapi implementasi pembagian menjadi hambatan sehingga pendekatan ini ditinggalkan
-
Percobaan 2: array biner berbasis bool
- Pendekatan kedua menyimpan bilangan sebagai array 0 dan 1
BigIntmemakai array[bool; 2048]- Karena mengalikan dua bilangan 1024-bit bisa membutuhkan ruang maksimum 2048-bit, maka disediakan 2048 bit
- Penjumlahan dan pengurangan diimplementasikan dengan gaya full adder
- Perkalian diproses dengan metode shift-and-add menggunakan sifat bilangan biner
- Pembagian diimplementasikan sebagai long division biner
- Dengan implementasi ini, bilangan prima 1024-bit pertama berhasil ditemukan, tetapi waktu eksekusinya sekitar 32 menit 44,90 detik
- Secara teknis target tercapai, tetapi tidak memenuhi batasan “waktu yang masuk akal”
-
Percobaan 3: chunk byte
- Lalu disadari bahwa setiap
booldalam array bool bukan memakan 1 bit, melainkan 1 byte [bool; 2048]berarti memakai 2048 byte, bukan 2048 bit- Setelah itu, formatnya diubah menjadi penyimpanan 2048 bit dalam array 256 byte
- Penjumlahan, pengurangan, dan perkalian tetap berjalan tanpa perubahan besar, sementara pembagian disesuaikan agar memperlakukan chunk byte seperti daftar bit
- Dengan cara ini, waktu pembuatan bilangan prima 1024-bit turun menjadi 4 menit 43 detik
- Lalu disadari bahwa setiap
-
Percobaan 4: chunk
u64- Cara chunk byte pada dasarnya adalah BigInt berbasis digit dengan radix lebih tinggi
- Pada langkah berikutnya, 2048 bit disimpan sebagai 32 chunk
u64 - Setiap chunk bertindak seperti satu “digit”
- Untuk menampung hasil perkalian dua chunk
u64, dipakaiu128 - Dengan struktur ini, bilangan 1024-bit bisa direpresentasikan dengan 16 chunk
u64alih-alih 309 digit desimal - Waktu pembuatan bilangan prima 1024-bit membaik menjadi 60~90 detik
Optimasi bottleneck
- Benchmark sederhana menunjukkan perbedaan yang jelas antara implementasi biner dan implementasi chunk
u64a + bdana - b: 5537.35ns → 123.57nsa * b: 1292283.14ns → 842.32nsa / bdana % b: 733446.76ns → 44440.12nsa < bdana > b: 2506.02ns → 58.91ns
- Setelah itu, optimasi difokuskan terutama pada pembagian, perkalian, operasi internal Miller-Rabin, dan logika pembangkitan kandidat
-
Pembagian
- Bottleneck terbesar adalah pembagian
- Bahkan pada struktur chunk
u64, pembagian yang lama masih melakukan long division satu bit demi satu bit - Dengan merujuk pada algoritme di halaman 598 Handbook of Applied Cryptography, diimplementasikan long division berbasis radix
- Caranya dengan memperkirakan “digit” quotient saat ini dari 3 “digit” terdepan dividend dan 2 “digit” terdepan divisor
- Implementasi ini menghemat sekitar 40.000ns per satu kali pembagian
- Jika divisor hanya terdiri dari satu chunk
u64, dilakukan penanganan khusus agar long division bisa dikerjakan lebih langsung denganu128 - Kasus seperti ini sering muncul di Miller-Rabin
-
Perkalian
- Perkalian menjadi sekitar 2 kali lebih cepat setelah loop disusun ulang untuk menghilangkan BigInt penyimpanan hasil antara
- Jumlah chunk yang terisi dihitung agar loop hanya berjalan pada chunk yang tidak nol
- Karena BigInt kebanyakan menyimpan bilangan di bawah 1024-bit, sering kali separuh dari ruang 2048-bit masih kosong
- Perkalian berbasis Karatsuba atau FFT juga dipertimbangkan, tetapi dianggap terlalu rumit untuk implementasi manual dan perkalian saat ini dinilai sudah cukup cepat
-
Optimasi internal Miller-Rabin
- Pada implementasi Miller-Rabin, fokusnya adalah mengurangi operasi yang mahal
- Alih-alih
x = mod_exp(x, 2, n), dilakukan langsungx = (x * x) % n mod_exp()pertama diganti dengan versi inline yang disederhanakan untuk mengurangi overhead pemanggilan fungsi- Untuk pengecekan genap, ditambahkan
num.is_even()agar tak perlu menghitung% 2 d / 2diubah menjadid >>= 1+= 1dan-= 1ditangani khusus lewatincrease()dandecrease()- Khususnya
is_even()dand >>= 1masing-masing memberi keuntungan sekitar 70.000ns - Dalam benchmark akhir, versi chunk
u64yang telah dioptimasi menjadi jauh lebih cepat a * b: 842.32ns → 295.04nsa / bdana % b: 44440.12ns → 831.77nsa / 2: 75121.58ns → 60.89nsa % 2 == 0: 78400.87ns → 21.65nsa - 1: 103.15ns → 67.54ns
Pembuat bilangan prima 1024-bit final
- Fungsi final pertama-tama membaca bilangan acak 1024-bit dari
/dev/urandom- Bit paling atas diaktifkan untuk memastikan ukurannya 1024-bit
- Bit paling bawah diaktifkan untuk memastikan bilangannya ganjil
- Setelah itu, alih-alih membaca bilangan acak baru setiap kali, nilai kandidat dinaikkan
2untuk berpindah ke kandidat ganjil berikutnyaincrease_by_2()pada umumnya hanya melakukan penjumlahan pada satu chunku64
- Sebelum Miller-Rabin, lebih dulu dilakukan trial division dengan daftar bilangan prima kecil
- Pada kode final, dipakai 1000 bilangan prima kecil pertama
- Karena bilangan prima kecil ini muat dalam satu chunk
u64, penanganan khusus pembagian satu chunk yang cepat bisa dimanfaatkan
- Masalah ini bisa ditangani sebagai bentuk embarrassingly parallel yang tidak memerlukan memori bersama atau sinkronisasi antarthread
- 16 thread CPU masing-masing mencari bilangan prima, lalu nilai dari thread yang pertama mengirim hasil dipakai
- Contoh eksekusi final mencatat elapsed time sekitar 0,086 detik
- Penggunaan CPU ditampilkan sebagai 690%
- Rata-rata 100 kali eksekusi adalah
0.04109 ± 0.00307detik- Artinya, bilangan prima 1024-bit ditemukan rata-rata dalam sekitar 40ms
- Pemanggilan
prime_1024bit()tunggal bisa bervariasi dari sekitar 8ms hingga sekitar 800ms karena sifat acak - Eksekusi paralel dipakai untuk memilih hasil tercepat dan meredam variasi tersebut
Kode dan keterbatasan
- Seluruh kode dan repositorinya dipublikasikan di github
- Tautan diskusinya ada di hackernews dan reddit
- Implementasi ini sulit dianggap aman secara kriptografis untuk penggunaan nyata, dan tujuannya juga bukan membuat pustaka kriptografi, melainkan lebih dekat ke eksperimen belajar dan implementasi
1 komentar
Pendapat Hacker News
Ada beberapa cryptocurrency yang memakai pencarian bilangan prima besar sebagai bagian dari fungsi proof of work, dan sekitar 8 tahun lalu, sekadar punya implementasi uji primalitas yang sangat cepat saja sudah bisa menghasilkan cukup banyak uang
Saya sempat menjadi penulis sekaligus pemelihara perangkat lunak penambangan riecoin untuk beberapa waktu; entah kenapa, mungkin karena saya memang suka bilangan prima
Tulisan ini melewatkan perkalian Montgomery, optimisasi nomor satu untuk uji primalitas cepat: https://en.m.wikipedia.org/wiki/Montgomery_modular_multiplic...
Ini menjadi dasar implementasi eksponensiasi modular berkecepatan tinggi yang praktis
Niall Emmart, yang seingat saya dulu berada di dunia akademik dan sekarang di Nvidia, merilis library bilangan bulat besar GPU CGBN yang benar-benar sangat cepat: https://github.com/NVlabs/CGBN
Sampai sekarang itu masih implementasi eksponensiasi modular batch tercepat yang saya tahu, dan kalau boleh sedikit nerd-out, hasilnya sampai membuat takjub
Suatu hari saya harus menulis cerita tentang bagaimana ini membuat saya mendominasi produksi sebuah cryptocurrency kecil selama sekitar 5 tahun. Dan Python punya eksponensiasi modular yang cukup bagus dalam bentuk tiga argumen
pow(x, y, m)untuk menghitungx^y % mDengan itu, kalau ingin membuat sendiri, uji primalitas Fermat atau Miller-Rabin bisa dibuat sangat mudah dan cukup menyenangkan. Kalau tidak ingin membuat sendiri,
mpz_probab_prime()dari library gmp juga bagus. gmp tentu lebih cepat, tetapi sulit mengalahkan keseruan uji Fermat dua baris saat bermain-main dengan bilangan prima besarIni berkaitan erat dengan eksponensiasi modular batch, tetapi bedanya bekerja di atas kurva eliptik, bukan modulo prima. Saya menganggapnya sebagai kelanjutan dari pekerjaan CGBN
Tahun lalu ia memberi presentasi yang bagus di seminar makan siang kriptografi Stanford, dan slide serta rekamannya juga tersedia online
https://cbr.stanford.edu/seminarTalks/slides_20230526_niall_...
https://www.youtube.com/watch?v=KAWlySN7Hm8
Apakah karena hanya ada gagasan samar bahwa kriptografi entah bagaimana memakai bilangan prima, tanpa tahu kapan dan mengapa dipakai, atau ada alasan yang lebih dalam?
pow(x,e,mod), saya pindah dari Perl ke PythonJika ada batas maksimum angka yang diberikan, membuat Miller-Rabin praktis deterministik itu sederhana
Cukup pilih basis-basis yang terbukti menyaring semua pseudoprime dalam rentang tersebut
Daftarnya juga tidak panjang. Miller-Rabin benar-benar kuat
Saya tidak menemukan jawabannya secara online
Satu baris inline assembly membuat perkalian bilangan bulat besar gaya sekolah dasar menjadi sederhana: https://github.com/jcalvinowens/toy-rsa/blob/master/bfi.c#L4...
Kalau bisa kembali ke masa lalu dan mengubah satu hal saja pada bahasa C, saya ingin memasukkan konsep perkalian melebar. Sayang Rust juga tidak memilikinya. Dukungan hardware ada di mana-mana. Cortex M0 bahkan tidak melakukan pembagian, tetapi punya perkalian melebar
Ini kode dari implementasi RSA mainan yang sangat jelek yang saya tulis lama sekali: https://github.com/jcalvinowens/toy-rsa
Alasan saya bisa bertahan hanya dengan uji Fermat adalah karena algoritmanya tidak akan bekerja jika bilangan-bilangan prima itu sebenarnya bukan prima. Uji Fermat cepat, dan satu kali enkripsi/dekripsi menghilangkan kemungkinan yang sangat kecil bahwa salah satunya adalah pembohong Fermat
Namun saya tidak tahu apakah bisa dibuktikan bahwa tidak ada pasangan kunci RSA yang dapat berhasil mengenkripsi/mendekripsi pesan dengan nilai P/Q yang bukan prima. Dalam implementasi nyata, tentu saja ini bukan cara yang baku, tetapi saya belum pernah menemukan jawabannya
C23 menambahkan tipe
_BitInt(N), dan misalnya untuk tipe 128 byte Anda bisa memakai_BitInt(1024)Namun dukungan compiler masih terbatas. Di Clang, agar N boleh lebih besar dari 128, Anda bisa memberi flag
-fexperimental-max-bitint-width=N. Jika N lebih besar dari 128 dan Anda membagi_BitInt(N), compiler akan crash begitu saja, tetapi+,-,*bekerja sesuai harapanAda built-in
@mulWithOverflowyang mengembalikan hasil beserta bit overflow, dan integer tersedia hingga(u|i)65535Bergantung pada apa yang dilakukan, setelah mendeteksi overflow Anda bisa menaikkannya ke tipe yang lebih besar, atau menaikkannya lebih dulu lalu memotongnya secara opsional
Ada juga operator terpisah
*|untuk perkalian saturating dan*%untuk perkalian wrapping. Itu bisa dipakai saat semantik seperti ini diperlukan. Overflow lainnya adalah perilaku tak terdefinisi yang termasuk pemeriksaan keselamatan, sehingga pada mode build Debug dan ReleaseSafe akan terjadi panicpdanqadalah bilangan Carmichael yang saling prima, RSA tetap bisa berhasil mengenkripsi dan mendekripsi pesanNamun
p*qakan memiliki faktor prima yang lebih kecil, sehingga lebih mudah difaktorkan dan keamanannya menurunPenasaran berapa lama pekerjaan ini memakan waktu
Saya pernah mengerjakan perkalian bilangan bulat besar sebagai proyek riset sarjana, dan itu memakan hampir dua semester. Saya mengimplementasikan Karatsuba, Toom-Cook, FFT kompleks, beberapa NTT, dan Schonhage-Strassen
Bilangan prima hampir seperti sihir matematika. Bagi yang tertarik, A Friendly Introduction to Number Theory karya Silverman adalah buku matematika yang sangat bagus
Sebagai catatan, tautan di halaman tertulis
4025051, bukan40250519Tulisan yang bagus. Saya juga baru-baru ini menulis sedikit kode big integer sendiri untuk versi awal [0], dan saya ingat betapa menjengkelkannya menerjemahkan penjelasan tingkat tinggi di makalah matematika menjadi operasi nyata
Namun ada sedikit keberatan
Jika menggunakan seluruh rentang
u64, angkanya bukan berbasis2^64-1, melainkan berbasis 2^64. Setiap word memiliki rentang dari 0 sampai2^64-1, sama seperti setiap digit desimal bernilai 0 sampai 9[0] https://github.com/LegionMammal978/bigfoot-sim
Seperti optimasi terakhir, jika saat gagal tidak membuat bilangan acak baru dan hanya menambah angka sebesar 2, keamanannya sedikit melemah
Karena bilangan prima tidak terdistribusi merata, ini akan bias ke arah bilangan prima yang berada tepat setelah celah prima yang besar
Ini adalah kompromi antara kecepatan eksekusi dan keacakan bilangan prima, dan saya memilih kecepatan karena menilai bahwa jika 16 thread masing-masing mulai dari bilangan acak lalu berlomba mencari prima, itu menambahkan keacakan yang cukup
Jika lebih menginginkan keacakan daripada kecepatan, mengganti
+=2dengan panggilanrng()adalah perubahan yang mudahTulisan yang bagus dan ditulis dengan baik
Sepertinya penulis bermaksud base-256, bukan
base-255Beberapa angka berukuran 1–2KB cukup muat di cache L1, dan kalaupun tidak, ada cache L2 berukuran lebih dari megabyte dengan waktu akses sekitar 3 ns
Di tulisan disebutkan bahwa kemungkinan harus menunggu baca/tulis RAM karena cache miss L1, tetapi bagian ini tidak dibahas lagi setelahnya
Selain itu, ini hanya membahas pembuatan bilangan prima, jadi sebagian besar jebakan RSA terhindari, dan
urandomseharusnya aman. Kalau kodenya bekerja dengan benar, tidak banyak hal yang bisa sangat keliruRSA punya beberapa isu terkait bilangan prima lemah yang perlu dihindari, tetapi saya tidak tahu apakah itu cukup umum untuk benar-benar menjadi masalah di sini
Ini mengingatkan saya pada proyek tahun pertama kuliah beberapa dekade lalu
Rekan proyek sekaligus teman saya, yang kemudian menjadi perwakilan lulusan, mengusulkan ide dan mengimplementasikan matematika intinya: membuat enkripsi RSA 4096-bit
Saya ingat betapa lambatnya pembuatan bilangan prima pada implementasi akhirnya. Di workstation PA-RISC, pembuatannya memakan sekitar 20 menit
Teman saya yang penggila matematika terus mengoptimalkan kode bahkan setelah proyek selesai, dan saya ingat ia membaca makalah tentang pengujian primalitas dan implementasi matematika big integer
Misalnya, ada peningkatan besar ketika pada perkalian komposisi, jika salah satu bilangan adalah 0, perkalian dilewati dan hasilnya langsung dibuat 0
Kalau tidak, harus menunggu lama, atau mengorbankan keamanan yang tahan hingga masa depan
Saya paham kenapa bit rendah disetel ke 1. Karena bilangan genap tidak pernah prima. Tentu saja 2 adalah pengecualian
Tapi saya tidak mengerti kenapa bit tinggi juga disetel ke 1. Saya bukan ahli bilangan prima atau kriptografi, tetapi ini terlihat seperti mengorbankan 1 bit entropi yang tidak perlu. Apa yang saya lewatkan?
Encoding byte dengan panjang variabel bisa menimbulkan masalah dalam pertukaran data antarperangkat lunak yang berbeda jika spesifikasinya tidak sangat jelas dan pengujiannya tidak matang
Lihat masalah yang muncul pada DHE berbasis RSA ketika kunci publik server memiliki nol di depan
Jika digit pertamanya 0, itu bukan angka dua digit
Tambahan lagi, dalam RSA kita mengalikan dua bilangan prima. Jika salah satunya 1024-bit, kalau ingatan saya benar, yang satunya sekitar 200-bit pun bisa mencapai jumlah bit entropi yang diperlukan untuk kunci
Jadi jika kedua bilangan prima dibuat 1024-bit, ada sedikit margin tambahan juga
Rasanya ini lebih aman daripada diminta bilangan prima 1024-bit lalu bingung apakah bilangan prima 1020-bit juga boleh. Sama seperti biasanya kita tidak menganggap
00042sebagai angka 5 digitSecara teknis, pilihan optimal bisa berbeda tergantung persisnya digunakan untuk apa, tetapi metode dalam tulisan itu tampak sebagai default yang lebih aman