Bagaimana saya bisa menyimpan kata sandi pengguna saya dengan aman?

169

Seberapa jauh ini lebih aman daripada MD5 biasa ? Saya baru saja mulai mencari keamanan kata sandi. Saya cukup baru di PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}
Rebar
sumber
Catatan php 5.4+ memiliki built in ini
Benjamin Gruenbaum
Juga lihat kerangka hashing kata sandi PHP (PHPass) Openwall. Ini portabel dan mengeras terhadap sejumlah serangan umum pada kata sandi pengguna.
jww
1
Wajib "menggunakan PDO bukannya interpolasi string", untuk orang-orang yang tersandung pada pertanyaan ini hari ini.
Dana Gugatan Monica

Jawaban:

270

Cara termudah untuk mengamankan skema penyimpanan kata sandi Anda adalah dengan menggunakan perpustakaan standar .

Karena keamanan cenderung menjadi jauh lebih rumit dan dengan lebih banyak kemungkinan yang tidak terlihat yang dapat dihadapi oleh sebagian besar programmer sendiri, menggunakan perpustakaan standar hampir selalu termudah dan paling aman (jika bukan satu-satunya) pilihan yang tersedia.


API kata sandi PHP baru (5.5.0+)

Jika Anda menggunakan PHP versi 5.5.0 atau lebih baru, Anda dapat menggunakan API hashing kata sandi baru yang disederhanakan

Contoh kode menggunakan API kata sandi PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Jika Anda masih menggunakan legacy 5.3.7 atau yang lebih baru, Anda dapat menginstal ircmaxell / password_compat untuk memiliki akses ke fungsi-fungsi bawaan )


Memperbaiki hash asin: tambahkan lada

Jika Anda menginginkan keamanan tambahan, petugas keamanan sekarang (2017) merekomendasikan menambahkan ' lada ' ke hash kata sandi asin (otomatis).

Ada penurunan sederhana di kelas yang mengimplementasikan pola ini dengan aman, saya sarankan: Netsilik / PepperedPasswords ( github ).
Muncul dengan Lisensi MIT, sehingga Anda dapat menggunakannya sesuka Anda, bahkan dalam proyek berpemilik.

Contoh kode menggunakan Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


Perpustakaan standar LAMA

Harap dicatat: Anda seharusnya tidak membutuhkan ini lagi! Ini hanya di sini untuk tujuan sejarah.

Lihatlah: Kerangka kerja hashing kata sandi PHP portabel : phpass dan pastikan Anda menggunakan CRYPT_BLOWFISHalgoritma jika memungkinkan.

Contoh kode menggunakan phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass telah diimplementasikan dalam beberapa proyek yang cukup terkenal:

  • phpBB3
  • WordPress 2.5+ dan juga bbPress
  • rilis Drupal 7, (modul tersedia untuk Drupal 5 & 6)
  • lainnya

Hal yang baik adalah Anda tidak perlu khawatir tentang perinciannya, perincian itu telah diprogram oleh orang-orang yang berpengalaman dan ditinjau oleh banyak orang di internet.

Untuk informasi lebih lanjut tentang skema penyimpanan kata sandi, baca posting blog Jeff : Anda Mungkin Menyimpan Kata Sandi Secara Tidak Benar

Apa pun yang Anda lakukan jika Anda menggunakan pendekatan ' Saya akan melakukannya sendiri, terima kasih ', jangan gunakan MD5atau SHA1lagi . Mereka adalah algoritma hashing yang bagus, tetapi dianggap rusak untuk tujuan keamanan .

Saat ini, menggunakan crypt , dengan CRYPT_BLOWFISH adalah praktik terbaik.
CRYPT_BLOWFISH dalam PHP adalah implementasi hash Bcrypt. Bcrypt didasarkan pada cipher blok Blowfish, menggunakan setup kunci yang mahal untuk memperlambat algoritma.

Jacco
sumber
29

Pengguna Anda akan jauh lebih aman jika Anda menggunakan kueri yang diparameterisasi daripada menggabungkan pernyataan SQL. Dan garam harus unik untuk setiap pengguna dan harus disimpan bersama dengan hash kata sandi.

Anton Gogolev
sumber
1
Ada artikel bagus tentang keamanan di PHP di Nettuts +, salting kata sandi juga disebutkan. Mungkin Anda harus melihatnya di: net.tutsplus.com/tutorials/php/…
Fábio Antunes
3
The Nettuts + adalah artikel yang sangat buruk untuk digunakan sebagai model - ini termasuk penggunaan MD5 yang dapat dengan kasar dipaksa dengan sangat mudah bahkan dengan garam. Alih-alih, cukup gunakan perpustakaan PHPass yang jauh, jauh lebih baik daripada kode apa pun yang Anda temukan di situs tutorial, yaitu jawaban ini: stackoverflow.com/questions/1581610/…
RichVel
11

Cara yang lebih baik bagi setiap pengguna untuk memiliki garam yang unik.

Manfaat memiliki garam adalah membuat penyerang kesulitan membuat tanda tangan MD5 dari setiap kata kamus. Tetapi jika seorang penyerang mengetahui bahwa Anda memiliki garam yang tetap, mereka kemudian dapat membuat sebelumnya tanda tangan MD5 dari setiap kata kamus yang diawali oleh garam tetap Anda.

Cara yang lebih baik adalah setiap kali pengguna mengubah kata sandi mereka, sistem Anda menghasilkan garam acak dan menyimpan garam itu bersama dengan catatan pengguna. Itu membuat sedikit lebih mahal untuk memeriksa kata sandi (karena Anda perlu mencari garam sebelum Anda dapat menghasilkan tanda tangan MD5) tetapi itu membuat jauh lebih sulit bagi penyerang untuk membuat pra-menghasilkan MD5.

R Samuel Klatchko
sumber
3
Garam biasanya disimpan bersama dengan hash kata sandi (misalnya output dari crypt()fungsi). Dan karena Anda harus mengambil hash kata sandi, menggunakan garam khusus pengguna tidak akan membuat prosedur lebih mahal. (Atau maksud Anda menghasilkan garam acak baru itu mahal? Saya tidak berpikir begitu.) Kalau tidak +1.
Insya Allah
Untuk tujuan keamanan, Anda mungkin ingin memberikan akses ke tabel hanya melalui prosedur tersimpan dan mencegah agar hash tidak pernah dikembalikan. Sebaliknya, klien melewati apa yang dianggapnya sebagai hash dan mendapatkan bendera sukses atau gagal. Ini memungkinkan proc yang disimpan untuk mencatat upaya, membuat sesi, dll.
Steven Sudit
@ Insya Allah - jika semua pengguna memiliki garam yang sama, maka Anda dapat menggunakan kembali serangan kamus yang Anda gunakan pada user1 terhadap user2. Tetapi jika setiap pengguna memiliki garam unik, Anda perlu membuat kamus baru untuk setiap pengguna yang ingin Anda serang.
R Samuel Klatchko
@R Samuel - itulah sebabnya saya memilih jawaban Anda, karena itu merekomendasikan strategi praktik terbaik untuk menghindari serangan seperti itu. Komentar saya dimaksudkan untuk mengungkapkan kebingungan saya tentang apa yang Anda katakan mengenai biaya tambahan garam per pengguna, yang saya tidak mengerti sama sekali. (karena "garam biasanya disimpan bersama dengan hash kata sandi" penyimpanan tambahan dan persyaratan CPU untuk garam per pengguna sangat mikroskopis, sehingga mereka bahkan tidak perlu disebutkan ...)
Insya Allah
@ Insya Allah - Saya sedang berpikir tentang kasus di mana Anda memiliki database diperiksa jika kata sandi hash tidak apa-apa (maka Anda memiliki satu pengambilan db untuk mendapatkan garam dan akses db kedua untuk memeriksa kata sandi hash). Anda benar tentang kasus di mana Anda mengunduh kata sandi garam / hash dalam satu pencarian dan kemudian melakukan perbandingan pada klien. Maaf bila membingungkan.
R Samuel Klatchko
11

Dengan PHP 5.5 (apa yang saya jelaskan tersedia untuk versi yang bahkan lebih awal, lihat di bawah) di sudut saya ingin menyarankan untuk menggunakan solusi bawaan yang baru: password_hash()and password_verify(). Ini memberikan beberapa opsi untuk mencapai tingkat keamanan kata sandi yang Anda butuhkan (misalnya dengan menentukan parameter "biaya" melalui $optionsarray)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

akan kembali

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Seperti yang mungkin Anda lihat, string berisi garam serta biaya yang ditentukan dalam opsi. Ini juga berisi algoritma yang digunakan.

Karena itu, ketika memeriksa kata sandi (misalnya saat pengguna masuk), saat menggunakan pelengkap password_verify() fungsi itu akan mengekstrak parameter kripto yang diperlukan dari hash kata sandi itu sendiri.

Ketika tidak menentukan garam, hash kata sandi yang dihasilkan akan berbeda pada setiap panggilan password_hash() karena garam dihasilkan secara acak. Karenanya, membandingkan hash sebelumnya dengan hash yang baru dibuat akan gagal, bahkan untuk kata sandi yang benar.

Memverifikasi karya seperti ini:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

Saya berharap bahwa menyediakan fungsi-fungsi bawaan ini akan segera memberikan keamanan kata sandi yang lebih baik jika terjadi pencurian data, karena ini mengurangi jumlah pemikiran yang harus diprogram oleh programmer.

Ada perpustakaan kecil (satu file PHP) yang akan memberi Anda PHP 5.5 password_hashdi PHP 5.3.7+: https://github.com/ircmaxell/password_compat

akirk
sumber
2
Dalam kebanyakan kasus, lebih baik menghilangkan parameter garam. Fungsi ini menciptakan garam dari sumber acak sistem operasi, sangat kecil kemungkinan Anda dapat memberikan garam yang lebih baik sendiri.
martinstoeckli
1
Itu yang saya tulis, bukan? "Jika tidak ada garam yang ditentukan, itu dihasilkan secara acak, untuk alasan itu lebih disukai untuk tidak menentukan garam"
akirk
Sebagian besar contoh menunjukkan cara menambahkan kedua parameter, bahkan ketika tidak disarankan untuk menambahkan garam, jadi saya bertanya-tanya mengapa? Dan jujur ​​saja, saya hanya membaca komentar di balik kode, bukan pada baris berikutnya. Ngomong-ngomong, bukankah akan lebih baik ketika contoh menunjukkan cara menggunakan fungsi terbaik?
martinstoeckli
Anda benar, saya setuju. Saya telah mengubah jawaban saya sesuai dan berkomentar di telepon. Terima kasih
akirk
bagaimana saya harus memeriksa apakah kata sandi yang disimpan dan kata sandi yang dimasukkan adalah sama? saya menggunakan password_hash()dan password_verifytidak peduli apa kata sandi (benar atau tidak) saya menggunakan saya berakhir dengan kata sandi yang benar
Brownman Revival
0

Tidak masalah dengan saya. Mr Atwood menulis tentang kekuatan MD5 terhadap tabel pelangi , dan pada dasarnya dengan garam panjang seperti itu Anda duduk cantik (meskipun beberapa tanda baca / angka acak, itu bisa memperbaikinya).

Anda juga dapat melihat SHA-1, yang tampaknya semakin populer akhir-akhir ini.

nickf
sumber
6
Catatan di bagian bawah pos Mr Atwood (merah) menghubungkan ke pos lain dari seorang praktisi keamanan yang menyatakan menggunakan MD5, SHA1 dan hash cepat lainnya untuk menyimpan kata sandi sangat salah.
sipwiz
2
@Matthew Scharley: Saya tidak setuju bahwa upaya tambahan yang dikenakan oleh algoritma hashing kata sandi yang mahal adalah keamanan palsu. Ini untuk menjaga dari kekerasan kata sandi yang mudah ditebak. Jika Anda membatasi upaya login, maka Anda melindungi terhadap hal yang sama (meskipun sedikit lebih efektif). Tetapi jika musuh memiliki akses ke hash yang disimpan DB, ia akan dapat dengan kasar memaksa kata sandi (mudah ditebak) dengan cukup cepat (tergantung pada seberapa mudah ditebak). Default untuk algoritma crypt SHA-256 adalah putaran 10.000, sehingga akan membuatnya 10.000 kali lebih sulit.
Insya Allah
3
Hash lambat sebenarnya dibuat dengan iterasi yang cepat dalam jumlah sangat besar, dan mengocok data di antara setiap iterasi. Tujuannya adalah untuk memastikan bahwa walaupun orang jahat mendapatkan salinan hash kata sandi Anda, ia harus membakar cukup banyak waktu CPU untuk menguji kamusnya terhadap hash Anda.
caf
4
@caf: Saya percaya algoritma bcrypt memanfaatkan kemahalan parameterizable dari penjadwalan kunci Eksblowfish; tidak sepenuhnya yakin bagaimana ini bekerja, tetapi penjadwalan kunci sering kali merupakan operasi yang sangat mahal yang dilakukan selama init sebuah objek konteks cipher, sebelum enkripsi dilakukan.
Insya Allah
3
Insya Allah: Ini benar - algoritma bcrypt adalah desain yang berbeda, di mana crypto primitive yang mendasarinya adalah blok cipher daripada fungsi hash. Saya merujuk ke skema berdasarkan fungsi hash, seperti MD5 crypt ().
caf
0

Saya ingin menambahkan:

  • Panjang kata sandi pengguna tidak dibatasi

Untuk kompatibilitas dengan sistem lama sering menetapkan batas untuk panjang maksimum kata sandi. Ini adalah kebijakan keamanan yang buruk: jika Anda menetapkan batasan, atur hanya untuk panjang minimum kata sandi.

  • Jangan kirim kata sandi pengguna melalui email

Untuk memulihkan kata sandi yang terlupakan Anda harus mengirim alamat yang dengannya pengguna dapat mengubah kata sandi.

  • Perbarui hash kata sandi pengguna

Kata sandi hash mungkin ketinggalan zaman (parameter algoritma dapat diperbarui). Dengan menggunakan fungsi ini password_needs_rehash()Anda dapat memeriksanya.


sumber