Secure coding: memproses password

Topik menyimpan password ini sebenarnya topik sederhana, tapi banyak developer yang tidak tahu. Di artikel ini akan saya jelaskan bagaimana cara yang baik menyimpan password dari mulai sejak dimasukkan user, sampai masuk ke database. Saya juga akan menjelaskan mengenai kemungkinan kebocoran password di file log, baik log aplikasi maupun log server (web server dan mungkin server lain).

Kecepatan hash di GPU saya yang sudah relatif tua
Kecepatan hash di GPU saya yang sudah relatif tua

Di tulisan ini saya banyak menggunakan kata “jika mungkin”. Alasannya adalah:

  • kadang sistem perlu diintegrasikan dengan sistem yang sudah ada, jadi beberapa hal tidak bisa diubah tanpa mengubah sistem yang sudah ada
  • kadang library yang dibutuhkan tidak tersedia di bahasa/teknologi yang dipakai
  • kadang ada batasan tertentu (requirement dari user/atasan/pemerintah)
  • kadang ada batasan hardware (misalnya algoritma terlalu kompleks untuk embedded system)

Menerima password dari user

Password sebaiknya dibaca dengan API/fungsi yang sudah disediakan untuk tujuan itu. Contohnya di form web kita memakai input type=”password”, di program Java versi console kita bisa memakai Console.readPassword. Jangan sok pintar membuat sendiri form input (misalnya dengan manual membaca key satu karakter demi satu karakter), atau membuat perilaku yang tidak standar.

Sekarang ini OS Mobile (iOS dan Android) memiliki password manager built in, tapi ini tidak berfungsi jika form login tidak standar. Jika memang ingin membuat yang custom, pastikan bisa bekerja dengan password manager standard milik sistem.

Jika password merupakan bagian dari login, tampilkan form user dan password di satu layar. Ini akan membantu password manager mengetahui password mana yang bisa dimasukkan secara otomatis. Saat ini sebagian password manager sudah bisa menangani form yang terdiri dari banyak halaman (halaman pertama hanya isian user/email, dan halaman kedua pertanyaan password), tapi secara umum hal ini kurang baik.

Password di memori

Password sebaiknya ada di memori hanya ketika dibutuhkan saja. Setelah password diproses atau dipakai, sebaiknya segera dihapus dari memori. Jika tidak dihapus, ada kemungkinan password bisa diekstrak dari memori: bisa dengan debugger, atau kadang jika program crash dan tercipta file core (memori dump), maka file itu bisa mengandung password.

Untuk bahasa yang memakai tipe string immutable dan tanpa garbage collection (kebanyakan bahasa dinamik seperti Java), jangan simpan password dalam bentuk string, tapi dalam bentuk array of bytes, yang nilainya bisa ditimpa. Alasannya adalah: kita tidak tahu kapan object akan dibuang dari memori.

Password ketika transit

Untuk aplikasi yang terkoneksi ke server, biasanya kita perlu mengirimkan password ke server. Gunakan protokol secure untuk pengecekan apakah password benar atau salah. Saat ini ada protokol khusus dalam kategori PAKE (Password authenticated key agreement), seperti SRP (dipakai oleh Apple iCloud), dan OPAQUE. Mungkin di artikel lain bisa saya jelaskan tentang ini, tapi intinya dengan PAKE, kita bisa mengecek apakah password benar atau salah tanpa mengirimkan passwordnya.

Tapi jika tidak mengerti mengenai protokol tersebut atau librarynya tidak tersedia, sebaiknya gunakan cara standar: kirim password melalui HTTPS, dan untuk aplikasi mobile implementasikan SSL Pinning. Beberapa orang mengimplementasikan sendiri pengiriman password, dengan cara dihash, lalu hashnya di compare dengan database, varian ini bisa berbahaya di kasus tertentu (akan dijelaskan lebih lanjut dalam bagian penyimpanan password).

Logging dan password

Pastikan log di server tidak menyimpan password. Ini mulai dari log load balancer, log web server, log aplikasi, log database, dan semua sistem lain yang berhubungan dengan aplikasi tersebut. Bahkan log console di browser juga harus dicek. Saya pernah mendapati ada aplikasi yang melakukan logging di console browser dan file JavaScript yang sama dipakai di versi mobile dan hasilnya logging muncul di log Android.

Cegah Brute Force

Penyerang yang menargetkan orang yang spesfik bisa memakai berbagai cara yang spesifik untuk orang tersebut (misalnya menyerang komputernya, menipu orangnya dsb). Tapi jika seorang penjahat ingin mendapatkan password untuk banyak orang, maka yang ditargetkan adalah aplikasinya.

Dengan asumsi bahwa database aman dari penyerang, maka jalan masuk satu-satunya adalah masuk menggunakan password yang benar. Dan jika tidak ada bug parah seperti SQL Injection dan authentication bypass, maka cara yang dipakai biasanya adalah brute force alias mencoba-coba password.

Untuk mencegah bruteforce, kita bisa memakai captcha, atau sistem yang akan memblokir salah login setelah N kali. Sebaiknya salah login ini tidak mendisable user secara permanen, tapi 30 menit (atau sehari), tujuannya adalah mengurangi jumlah percobaan password.

Sistem yang mendisable user secara permanen justru bisa menyulitkan admin. Contohnya: penjahat iseng mencoba-coba login semua orang 10 kali, hasilnya semua orang tidak bisa login sama sekali. Dalam kasus ini admin jadi repot harus turun tangan mereset password semua orang atau mengaktifkan kembali account semua orang.

Pastikan juga semua akses untuk autentikasi dibatasi, jadi misalnya ada login via web dan via API untuk mobile, pastikan keduanya dibatasi. Cukup satu channel yang tidak dibatasi, maka seseorang bisa bruteforce passwordnya. Contoh realnya: beberapa sistem membatasi password salah via webmail, tapi tidak membatasi via IMAP.

Menyimpan hash password di database

Untuk bagian berikut ini, serangan yang diasumsikan adalah: penyerang mendapatkan akses database password. Ini bisa jadi karena ada bug di aplikasi, ataupun bug di sistem lain sehingga database bocor.

Jangan simpan password di database, simpanlah hashnya. Agar lebih aman, gunakan salt dan pepper. Pertama akan saya bahas dulu apa maksudnya menyimpan hash. Hanya dalam kasus sangat khusus saja kita perlu menyimpan password (dan bukan hashnya).

Fungsi hash akan memetakan sebuah nilai (biasanya string) ke nilai lain (biasanya angka). Fungsi hash bisa dipakai di struktur data Dictionary/Hashtable. Ada kelompok fungsi hash khusus yang namanya “cryptographic hash” yang akan memetakan string ke bilangan, sedemikian hingga kita tidak bisa memetakan balik dari bilangan ke string aslinya. Contoh fungsi hash kriptografi yang ada adalah: md5, sha1, dsb.

Contoh realnya, dengan md5 string password dihash menjadi “5f4dcc3b5aa765d61d8327deb882cf99”. String sepanjang apapun juga jika dihash dengan md5 akan menjadi 32 karakter heksadesimal. Dengan fungsi secure hash, jika diketahui hashnya, maka kita tidak bisa mengembalikan lagi menjadi string asalnya.

Di Internet sudah banyak contoh kode program yang akan menghasilkan string hash (representasi heksadesimal dari bilangan hash) dari sebuah string (misalnya password). Ketika user memasukkan password untuk login, kita akan menghash password tersebut , lalu membandingkan dengan nilai hash yang disimpan di server.

Dengan hash saja, maka bisa dilakukan brute force attack atau dictionary attack. Intinya adalah: kita mencoba kata-kata satu demi satu yang dihash lalu dicocokkan hasil hashnya dengan yang tersimpan. Selain itu: jika dua user memiliki password sama, akan bisa ketahuan karena hasil hashnya sama. Jika password satu user bocor, user lain yang hashnya sama akan ketahuan juga.

Untuk mengatasi masalah itu, kita bisa menambahkan string sebelum melakukan hashing, string ini disebut dengan salt. Pertama: buat string random (tiap user perlu berbeda), misalnya “xyz”, tambahkan dengan password user menjadi “xyzpassword”. Di database kita simpan nilai saltnya “xyz” dan nilai hashnya. Misalnya jika md5 maka hash “xyzpassword” adalah da30e9a6ff2111ace362713a92c43a04, maka di database simpanlah “xyz” dan hashnya. Jika ada user lain dengan password sama, tapi saltnya berbeda, maka akan terlihat bahwa hashnya berbeda.

Ketika database bocor, attacker tetap bisa mengcrack, tapi butuh waktu lebih lama, karena tiap user harus dicrack terpisah karena salt-nya berbeda. Supaya lebih sulit, kita bisa menambahkan pepper, yaitu string khusus per aplikasi jadi yang dihash adalah “salt+pepper+password”). Jika yang bocor hanya databasenya saja (misalnya karena SQL injection) dan file konfigurasi tidak bocor, maka password tetap aman. Jadi bedanya salt dan pepper adalah: salt sifatnya tidak rahasia, disimpan di database. Sedangkan pepper sifatnya rahasia dan disimpan di luar database.

Masalah dengan fungsi hash yang umum seperti md5/sha1/dll adalah: tidak dirancang untuk password, jadi attacker bisa melakukan brute force dengan cepat (jutaan password per detik bisa ditest), agar lebih sulit lagi: gunakan algoritma hashing password yang lebih aman lagi: Password Based Key Derivation, seperti scrypt (atau Argon, atau yang lain, saat ini Argon dan scrypt dianggap sangat baik).

Saya pernah mendapati ada pihak yang melakukan hal yang aneh:

  • password dihash di database
  • password tidak dikirimkan oleh aplikasi, tapi dihash oleh JavaScript dan dikirimkan hashnya
  • hasil hash Javascript dicocokkan dengan database

Dalam kasus ini: ini sama saja menyimpan password dengan plaintext, karena attacker cukup mengirimkan hashnya langsung tanpa tahu passwordnya.

Menyimpan Password dengan fitur OS

Beberapa aplikasi terpaksa harus menyimpan password user. Contoh kasusnya adalah berbagai aplikasi yang menyimpan password untuk email, FTP, database, dsb. Dalam kasus ini password perlu dienkripsi.

Berbagai sistem operasi memiliki cara masing-masing untuk menyimpan data secara aman. Sayangnya tidak semuanya caranya mudah. iOS dan macOS memiliki keychain untuk menyimpan password (dan bisa diunlock dengan biometrik), Android memiliki Keystore tapi lebih kompleks dari iOS (terutama jika perlu mensupport Android versi lama).

Untuk mudahnya: gunakan berbagai Library yang sudah ada untuk menyimpan password secara aman.

Change Password

User perlu bisa mengganti passwordnya. Hal yang harus dipastikan adalah: sistem perlu mengecek bahwa password lama diverifikasi. Beberapa sistem tidak melakukan hal ini (hanya langsung menerima password baru), dan ini berbahaya.

Jika ada perubahan password, berikan notifikasi ke user via email. Jika ada bug sehingga orang yang mengubah password user, maka user akan mendapatkan notifikasi dan segera mengetahui masalah keamanan tersebut. Selain itu email bisa menjadi catatan bagi user bahwa password sudah pernah diubah.

Reset/Forgot password

Fungsi berikutnya yang berhubungan dengan password adalah: reset password. Secara umum yang sebaiknya dilakukan adalah:

  • menghasilkan token untuk reset password, token ini perlu ada expirationnya
  • token hanya boleh digunakan untuk reset password orang itu saja
  • token tidak boleh mudah ditebak

Beberapa contoh kesalahan yang pernah saya lihat:

  • token tidak memiliki expiration, jadi jika ketemu token lama di email user, maka password bisa direset dengan token lama tersebut
  • token mudah ditebak (misalnya ternyata hanya string nama user yang dihash, atau string nama user yang diencode dengan base64)
  • password langsung berubah ketika orang menekan “forgot password” dan link reset password langsung diemail ke user. Hasilnya: jika ada orang iseng mengisikan forgot password, maka password user akan langsung ter-reset dan user harus repot mengganti passwordnya sendiri.

OAuth

Beberapa sistem mendukung OAuth atau sistem sejenis sehingga user bisa memberikan akses ke aplikasi tanpa memberikan password. Detailnya terlalu panjang untuk artikel ini, jadi saya berikan gambaran singkatnya dengan contoh saja. Contohnya jika kita memakai Facebook login:

  • Website yang ingin menerima login Facebook harus mendaftarkan diri ke facebook dan mendapatkan key
  • Ketika akan login ke sebuah website, user ditanya oleh Facebook: “Apakah Anda mau login ke website ini?”
  • Jika user menjawab “iya” dan belum login di Facebook, maka user akan diminta login ke Facebook dengan user dan passwordnya
  • Facebook akan memberikan token ke aplikasi untuk dipakai sebagai metode autentikasi

Dengan cara ini, website bisa menerima login Facebook tanpa harus menyimpan password sama sekali. Jika database bocor, maka tidak ada password yang bocor karena memang tidak disimpan.

Penutup

Tulisan ini hanya sekedar berisi best practice dalam menangani password. Jika ini diimplementasikan, maka akan meningkatkan keamanan, tapi tidak menjamin sebuah sistem akan 100% aman. Masih ada banyak kemungkinan logic bug dan berbagai detail kecil lain yang terlewatkan.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.