Anda di sini

Pemrograman

[ULASAN] 5 Point Penting Seorang Sistem Desainer Wajib Ketahui Dalam Mengamankan Sandi Pengguna

Aditya Suranata - 20 November 2015 14:37:22 0

Pada artikel ini, saya akan menjelaskan teori untuk menyimpan sandi pengguna dengan aman pada sistem apapun yang akan kita bangun, begitu juga dengan beberapa kode contoh yang ditulis dalam bahasa Python menggunakan pustaka Bcrypt, serta kesalahan dari solusi-solusi umum yang sering orang gunakan dalam teknik penyimpanan sandi pengguna.

Solusi Buruk #1: sandi dalam teks polos (plain text password)

Akan menjadi sangat tidak aman untuk menyimpan setiap sandi pengguna dalam bentuk teks polos pada database Anda:

nama penggunasandi teks polos
ayu@yahoo.com085338576282
bayu@gmail.comrahasia123
......

Ini tidak aman karena jika hacker mendapat akses ke database Anda, mereka bisa menggunakan sandi tersebut untuk login sebagai pengguna yang bersangkutan pada sistem Anda. Atau bahkan lebih parahnya, jika pengguna tersebut menggunakan sandi yang sama untuk situs lain di Internet, hacker juga dapat login kesana dan meretas akun pengguna tersebut. Anda tidak senang, pengguna sistem Anda lebih tidak senang lagi.

(Jika Anda berpikir tidak seorang pun akan menyimpan sandi pengguna layanannya dengan cara ini, Anda salah, karena perusahaan raksasa Sony melakukan hal ini pada tahun 2011).

Solusi Buruk #2: sha1(sandi)

Solusi yang sedikit lebih baik adalah menyimpan sandi dalam bentuk "one-way hash" (hash satu arah), biasanya menggunakan fungsi seperti md5() atau sha1() dalam bahasa pemrograman:

nama penggunasha1(sandi)
ayu@yahoo.comc62d29cf871c8d5a590cd8c34b7bc97405388221
bayu@gmail.com68bd72cfcd18bd2c3c781bbced1c59fb4dd67c03
......

Meskipun server tidak menyimpan sandi teks polos dimanapun, server masih tetap dapat mengenali pengguna dengan fungsi yang sama:

def is_password_correct(user, password_attempt):
    return sha1(password_attempt) == user["sha1_password"]

Solusi ini lebih aman ketimbang menyimpan sandi teks polos, karena secara teori hal ini tidak memungkinkan untuk membalik (undo) fungsi hash satu arah dan mencari sebuah string input yang outputnya menjadi nilai hash yang sama. Tapi sayangnya, para hacker juga telah menemukan jalan untuk hal ini.

Satu masalahnya adalah banyak fungsi hash (termasuk md5() dan sha1() ) sebenarnya tidaklah terlalu "satu arah", dan para pakar keamanan menyarankan untuk fungsi tersebut tidak lagi digunakan untuk aplikasi keamanan. (Sebaiknya, Anda disarankan menggunakan fungsi hash yang lebih baik seperti sha256() yang mana sejauh ini tidak diketahui memiliki kerentanan.)

Namun ada masalah yang lebih besar: para hacker sedikit pun tidak perlu untuk membalik fungsi hash; mereka cukup terus menebak sandi input hingga mereka menemukan kecocokan. Ini mirip dengan mencoba semua kombinasi dari kunci kombinasi. Ini adalah contoh seperti apa kodenya:

database_table = {
    "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8": "ayu@yahoo.com",
    "cbfdac6008f9cab4083784cbd1874f76618d2a97": "bayu@gmail.com",
    ...}
 
for password in LIST_OF_COMMON_PASSWORDS:
    if sha1(password) in database_table:
        print "Hacker wins! I guessed a password!"

Anda mungkin berfikir bahwa terlalu banyak kemungkinan sandi untuk teknik ini untuk dapat dinyatakan layak. Tapi ada sandi umum yang jauh lebih sedikit dari apa yang Anda bayangkan. Kebanyakan orang menggunakan sandi yang berasal dari kata-kata kamus (mungkin dengan sedikit nomer atau huruf yang ditambahkan ekstra). Dan kebanyakan dari fungsi hash seperi sha1() dapat dijalankan dengan sangat cepat -- satu komputer secara harfiah dapat mencoba milyaran kombinasi setiap detiknya. Hal itu berarti kebanyakan sandi dapat ditebak hanya dalam 1 jam-cpu. Program seperti John The Ripper dapat melakukan hal semacam ini.

Disamping itu: tahun dahulu, komputer tidak secepat sekarang, jadi para hacker yang tergabung dalam komunitas membuat tabel pelangi (rainbow table) yang sebelumnya telah memiliki pra-computed set dalam jumlah besar dari hash-hash tersebut. Hari ini, tidak seorang pun menggunakan lagi tabel pelangi karena komputer sudah cukup cepat tanpanya.

Jadi berita buruknya adalah setiap pengguna dengan sandi sederhana seperti "rahasia" atau "rahasia10121993" atau setiap milyaran sandi sederhana akan mendapat sandi mereka ditebak dengan mudah oleh hacker. Jika Anda memiliki sandi yang sangat rumit (lebih dari 16 huruf dan angka acak) Anda mungkin aman.

Juga perlu dicatat bahwa kode diatas secara efektif menyerang semua sandi pada saat yang bersamaan. Tidak peduli jika ada 10 pengguna dalam database Anda, atau 10 juta, hal itu tidak memperlambat sang hacker untuk menebak sandi yang cocok. Semua yang penting adalah seberapa cepatkah sang hacker dapat mengiterasi melalui sandi yang potensial. (Dan faktanya, memiliki banyak data pengguna dalam database akan membantu hacker, karena seperti biasanya akan ada seseorang di sistem yang menggunakan "rahasia123" sebagai sandi mereka.)

sha1(sandi) adalah apa yang biasa digunakan oleh LinkedIn untuk menyimpan sandi pengguna mereka. Dan pada tahun 2012, set besar dari sandi dalam bentuk hash tersebut bocor ke publik. Seiring waktu, hacker berhasil menemukan sandi teks polos dari kebanyakan hash yang berhasil dibobol tersebut.

Kesimpulan: menyimpan sandi dalam bentuk hash sederhana (tanpa menggunakan salt) tidaklah aman -- jika ada hacker mendapat akses ke database Anda, mereka akan mampu menebak mayoritas dari sandi pengguna Anda.

Solusi Buruk #3: sha1(FIXED_SALT + sandi)

Salah satu usaha untuk membuat hal menjadi lebih aman adalah dengan "memberi tambahan garam" (salt) pada sandi sebelum di-hashing:

nama penggunasha1("salt123456789"+sandi)
ayu@yahoo.comaca1bf54587f0a8f2f45c914805b97eaf58ef817
bayu@gmail.combe90d8e8dad8128b3c63bc1b135c8dd4bf1f1efe
......

Salt seharusnya terdiri dari byte string acak berukuran panjang. Jika hacker mendapat akses ke hash password baru tersebut (tapi tidak dengan salt-nya), hal itu akan membuatnya sedikit lebih sulit untuk si hacker menebak sandi karena mereka juga perlu untuk mengetahui salt yang digunakan. Akan tetapi, jika hacker berhasil membobol masuk ke server Anda, mereka mungkin juga mendapatkan akses ke kode sumber sistem Anda, sehingga mereka juga akan bisa mempelajari salt yang Anda gunakan. Itulah mengapa desainer keamanan mengasumsikan kemungkinan terburuk, dan tidak bergantung pada salt sebagai kunci utamanya.

Tapi meskipun salt bukan merupakan rahasia, hal itu masih dapat menyulitkan penggunaan teknik kuno tabel pelangi yang saya bahas sebelumnya. (Tabel pelangi tersebut diciptakan dengan asumsi tidak ada salt pada hash sandi yang akan ditebak, jadi salt dapat menghentikannya.) Akan tetapi, semenjak tak seorang pun yang masih menggunakan tabel pelangi, menambahkan fixed salt tidak akan banyak membantu. Sang hacker masih bisa menjalankan foor-loop dasar yang sama dari atas:

for password in LIST_OF_COMMON_PASSWORDS:
    if sha1(SALT + password) in database_table:
        print "Hacker wins! I guessed a password!", password

Kesimpulan: menambahkan fixed salt masih tidak cukup aman.

Solusi Buruk #4: sha1(PER_USER_SALT + password)

Trobosan baru selanjutnya dalam keamanan adalah dengan membuat kolum baru dalam database dan menyimpan salt lain satu persatu untuk masing-masing pengguna. Salt dibuat secara acak ketika akun pengguna pertama kali dibuat (atau ketika pengguna mengganti sandi mereka).

nama penggunasaltsha1(salt + sandi)
ayu@yahoo.com2dc7fcc...1a74404cb136dd60041dbf694e5c2ec0e7d15b42
bayu@gmail.comafadb2f...e33ab75f29a9cf3f70d3fd14a7f47cd752e9c550
.........

Dan cara mengotentikasi pengguna tidak terlalu lebih sulit dari sebelumnya:

def is_password_correct(user, password_attempt):
    return sha1(user["salt"] + password_attempt) == user["password_hash"]

Dengan menggunakan salt berlainan untuk masing-masing pengguna, kita mendapat keuntungan yang besar: sang hacker tidak bisa menyerang semua sandi pengguna Anda dalam waktu yang bersamaan. Sebaliknya, kode serangnya harus bersusah payah menyerang setiap sandi pengguna satu persatu:

for user in users:
    PER_USER_SALT = user["salt"]
 
    for password in LIST_OF_COMMON_PASSWORDS:
        if sha1(PER_USER_SALT + password) in database_table:
            print "Hacker wins! I guessed a password!", password

Jadi pada dasarnya, jika Anda memiliki satu juta pengguna, menggunakan per-user-salt menyulitkan sang penyerang untuk menebak sandi dari semua pengguna Anda menjadi satu juta kali lebih sulit. Tapi ini masih belum mustahil dapat dilakukan oleh hacker. Alih-alih 1 jam-cpu, sekarang mereka memerlukan 1 juta jam-cpu, yang mana dapat dengan mudah untuk disewa dari Amazon dengan kisaran harga Rp.500juta-an.

Masalah riil dengan semua sistem yang telah kita bahas sejauh ini adalah bahwa fungsi hash seperti sha1() (atau bahkan sha256) dapat dijalankan pada sandi dengan laju 100J+/detik (atau bahkan lebih cepat, dengan menggunakan GPU). Walaupun fungsi hash tersebut didesain dengan keamanan yang telah dipikirkan, mereka juga mendesain agar fungsi tersebut berjalan cepat ketika dijalankan pada input yang lebih panjang seperti hasing sebuah file secara keseluruhan untuk memastikan integritas file. Dengan garis bawah: fungsi hash tersebut tidak didesain untuk digunakan pada penyimpanan sandi.

Solusi Baik: bcrypt(password)

Sebaliknya, tersedia set fungsi hash yang mana memang secara spesial didesain untuk sandi. Dengan tambahan untuk menjadi fungsi hash "satu-arah" yang aman, ia juga didesain untuk menjadi lambat.

Salah satu contoh adalah Bcrypt. bcrypt() membutuhkan sekitar 100milidetik (100ms) untuk menghitung, yang mana lebih lambat 10.000x ketimbang sha1(). 100ms masih cukup cepat sehingga pengguna tidak menyadari ketika mereka login, tapi cukup lambat sehingga kurang layak untuk menjalankan daftar pangjang kemungkinan sandi. Misalnya, jika seorang hacker ingin menghitung bcrypt() pada daftar milyaran kemungkinan sandi, itu akan membutuhkan waktu sekitar 30.000 jam-cpu (sekitar Rp.15jt) -- dan itu hanya untuk satu sandi. Jelas bukan tidak mungkin, tapi perlu kerja lebih dari kebanyakan hacker mau lakukan.

Jika Anda mempertanyakan bagaimana Bcrypt bekerja, disini adalah papernya. Pada dasarnya "trik"nya adalah ia menjalankan enkripsi internal/fungsi hash berulang kali didalam putaran. (Terdapat alternatif untuk Bcrypt, seperti PBKDF2 yang menggunakan trik yang sama.).

Juga, Bcrypt dapat disesuaikan, dengan parameter log_rounds yang memberitahunya berapa kali harus menjalankan fungsi hash internal tersebut. Jika secara tiba-tiba, Intel datang dengan komputer barunya yang 1000kali lebih cepat dari komputer yang ada hari ini, Anda dapat menyesuaikan sistem Anda untuk menggunakan log_rounds yang juga 10x lebih cepat dari yang sebelumnya (log_rounds adalah logaritmik), yang mana akan menyingkirkan komputer yang 1000x lebih cepat.

Karena bcrypt() sangat lambat, hal tersebut menjadikan konsep dari tabel pelangi kembali diperhitungkan oleh para penyerang, sehingga per-user-salt dibenamkan kedalam sistem Bcrypt. Faktanya, pustaka seperti py-bcrypt menyimpan salt pengguna pada string yang sama dengan hash sandi, jadi Anda bahkan tidak perlu lagi mengeluarkan usaha tambahan untuk membuat kolum khusus untuk menyimpan salt masing-masing pengguna pada database.

Mari perhatikan kodenya in action. Pertama, mari kita install (keluarga Debian Linux):

sudo apt-get install python-pip && sudo pip install pip --update && sudo pip install py-bcrypt

Sekarang py-bcrypt telah terinstall, berikut adalah kode Python yang akan Anda jalankan ketika membuat pengguna baru atau mereset sandi pengguna:

from bcrypt import hashpw, gensalt
plaintext_password = 'Ayu<3Bayu1000*'
hashed = hashpw(plaintext_password, gensalt())
print hashed    # save this value to the database for this user
'$2a$12$8vxYfAWCXe0Hm4gNX8nzwuqWNukOkcMJ1a9G2tD71ipotEZ9f80Vu'

Mari kita bedah sedikit output stringnya:

$2a$12$8vxYfAWCXe0Hm4gNX8nzwuqWNukOkcMJ1a9G2tD71ipotEZ9f80Vu

$bcrypt_id$log_rounds$128-bit-salt184-bit-hash

Seperti yang dapat Anda lihat, ia menyimpan keduanya, salt dan output hash menjadi satu dalam string. Ia juga menyimpan parameter log_rounds yang telah digunakan untuk menggenerate sandi, yang mana mengontrol seberapa banyak usaha (p.s. sebarapa lambat) yang dilakukan untuk menghitung. Jika Anda ingin hashnya menjadi lebih lambat, Anda lewatkan nilai yang lebih panjang ke gensalt():

hashed = hashpw(plaintext_password, gensalt(log_rounds=13))
print hashed
'$2a$13$ZyprE5MRw2Q3WpNOGZWGbeG7ADUre1Q8QO.uUUtcbqloU0yvzavOm'

Perhatikan bahwa sekarang 13 dari sebelumnya 12. Dalam kasus apapun, Anda menyimpan string ini dalam database, dan saat ketika pengguna yang sama mencoba untuk login, Anda menerima nilai hash yang sama dan melakukan ini:

if hashpw(password_attempt, hashed) == hashed:
    print "It matches"
else:
    print "It does not match"

Anda mungkin bertanya mengapa Anda lewatkan hash sebagai salt argument ke hashpw(). Alasannya adalah karena fungsi hashpw() cukup pintar, dan bisa mengekstrak salt dari string $2a$12$... tersebut. Hal ini bagus, karena itu berarti Anda tidak perlu lagi untuk menyimpan, mem-parsing atau menangani nilai salt apapun sendiri -- satu nilai yang perlu Anda hadapi hanya string hash tunggal yang mana berisikan semua hal yang Anda perlukan.

Final Thoughts: memilih sandi yang baik

Jika pengguna Anda memiliki sandi hanya terdiri dari satu kata klasik, "rahasia", maka tak ada jumlah hashing, salting, bcrypt, dll apapun yang akan mampu melindungi pengguna tersebut. Sang hacker akan mencoba sandi yang sederhana terlebih dahulu, jadi jika sandi Anda menjurus ke daftar atas kemungkinan sandi, sang hacker kemungkinan akan menebaknya.

Cara terbaik untuk mencegah sandi Anda dapat ditebak adalah dengan membuat sandi yang berada jauh dari daftar kemungkinan sandi sebisa mungkin. Sandi apapun yang berdasarkan pada kata-kata kamus (meskipun memiliki mutasi seperti huruf atau angka pada bagian akhir) akan berada pada daftar pertama dari tebakan beberapa juta password.

Sayangnya, sandi yang sulit ditebak adalah juga sandi yang sulit untuk diingat. Jika hal tersebut bukan menjadi masalah, saya lebih menyarankan untuk mengambil sandi yang terdiri dari 16 karakter dengan urutan acak dari huruf dan angka. Orang lain menyarankan menggunakan passphrase, seperti "anjingku baron ellow sudah berpulang lima tahun yang lalu". Jika sistem Anda membolehkan sandi panjang dengan spasi, maka ini sudah pasti lebih baik dari sebuah sandi seperti "baron100". (Tapi sebenarnya saya masih menduga kekuatan passphrase dari sebagian besar pengguna akan berakhir menjadi hampir sama dengan kekuatan sandi 8 karakter alfanumerik acak.) hanya berbeda tingkat kemudahannya untuk mengingat.

Semoga bermanfaat, salam.

Referensi: Dustin Boswell. Storing User Passwords Securely: hashing, salting, and Bcrypt. http://dustwell.com/how-to-handle-passwords-bcrypt.html

1.299
Image

Aditya Suranata

Aditya suka menulis, bukan hanya sekedar hobi, menulis menjadi medianya untuk mencurahkan pikiran dan perasaan. Di TutorKeren.com kebanyakan menyumbang tulisan sesuai dengan minat dan keahliannya yaitu pada kategori pemrograman dan elektronika. Selain itu juga gemar menulis mengenai hal-hal umum, seperti ilmu alam, sosial dan beberapa pengalamannya yang mungkin bisa berguna untuk orang lain.

Artikel Menarik Lainnya
Mari Gabung

Halo Emo 51 , Ada yang ingin disampaikan? Jangan sungkan untuk gabung diskusi ini. Silahkan Login dulu atau Daftar baru.