ฉันจะเก็บรหัสผ่านของผู้ใช้อย่างปลอดภัยได้อย่างไร


169

สิ่งนี้ปลอดภัยกว่าMD5ธรรมดาแค่ไหน ฉันเพิ่งเริ่มมองหาการรักษาความปลอดภัยรหัสผ่าน ฉันค่อนข้างใหม่กับ 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]!";
}

Note php 5.4+ มีสิ่งนี้ในตัว
Benjamin Gruenbaum

ดูที่กรอบการแฮชรหัสผ่าน PHPของ Openwall (PHPass) แบบพกพาและแข็งต่อการโจมตีทั่วไปจำนวนมากในรหัสผ่านผู้ใช้
jww

1
ข้อผูกมัด "ใช้PDOแทนการแก้ไขสตริง" สำหรับคนที่สะดุดในคำถามนี้ในวันนี้
คดีของกองทุนโมนิกา

คำตอบ:


270

วิธีที่ง่ายที่สุดในการทำให้รหัสผ่านของคุณปลอดภัยนั้นคือการใช้ไลบรารี่มาตรฐาน

เนื่องจากการรักษาความปลอดภัยมีแนวโน้มที่จะซับซ้อนมากขึ้นและมีความเป็นไปได้ที่มองไม่เห็นมากกว่าโปรแกรมเมอร์ส่วนใหญ่สามารถจัดการเพียงอย่างเดียวการใช้ไลบรารีมาตรฐานจึงเป็นวิธีที่ง่ายที่สุดและปลอดภัยที่สุด (ถ้าไม่ใช่)


API รหัสผ่าน PHP ใหม่ (5.5.0+)

หากคุณใช้ PHP เวอร์ชัน 5.5.0 หรือใหม่กว่าคุณสามารถใช้ API การแฮชรหัสผ่านแบบง่าย

ตัวอย่างของรหัสโดยใช้รหัสผ่าน API ของ 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';
}

(ในกรณีที่คุณยังคงใช้งานแบบดั้งเดิม 5.3.7 หรือใหม่กว่าคุณสามารถติดตั้งircmaxell / password_compatเพื่อให้สามารถเข้าถึงฟังก์ชั่นบิวด์อิน)


ปรับปรุงเมื่อ hashes เค็ม: เพิ่มพริกไทย

หากคุณต้องการความปลอดภัยเป็นพิเศษผู้รักษาความปลอดภัยในขณะนี้ (2017) ขอแนะนำให้เพิ่ม ' พริกไทย ' ลงในการแฮชรหัสผ่าน (โดยอัตโนมัติ)

มีคลาสดร็อปง่ายที่ใช้รูปแบบนี้อย่างปลอดภัยฉันแนะนำ: Netsilik / PepperedPasswords ( github )
มันมาพร้อมกับใบอนุญาต MIT เพื่อให้คุณสามารถใช้งานได้ตามที่คุณต้องการแม้ในโครงการที่เป็นกรรมสิทธิ์

ตัวอย่างของรหัสโดยใช้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';
}


ไลบรารีมาตรฐาน OLD

โปรดทราบ:คุณไม่ควรต้องการสิ่งนี้อีกต่อไป! นี่เป็นเพียงเพื่อจุดประสงค์ทางประวัติศาสตร์เท่านั้น

ลองดูที่: เฟรมเวิร์กการแฮชรหัสผ่าน PHP แบบพกพา : phpassและให้แน่ใจว่าคุณใช้CRYPT_BLOWFISHอัลกอริทึมถ้าเป็นไปได้

ตัวอย่างของรหัสโดยใช้ 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 ถูกนำไปใช้ในโครงการที่รู้จักกันดี:

  • phpBB3
  • WordPress 2.5+ เช่นเดียวกับ bbPress
  • รีลีส Drupal 7 (โมดูลสำหรับ Drupal 5 & 6)
  • คนอื่น ๆ

สิ่งที่ดีคือคุณไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดรายละเอียดเหล่านั้นได้รับการตั้งโปรแกรมโดยคนที่มีประสบการณ์และได้รับการตรวจสอบจากคนจำนวนมากบนอินเทอร์เน็ต

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแผนการเก็บรหัสผ่านอ่านโพสต์บล็อกของJeff : คุณอาจเก็บรหัสผ่านไม่ถูกต้อง

สิ่งที่คุณทำถ้าคุณไปสำหรับ ' ฉันจะทำมันด้วยตัวเองขอขอบคุณวิธีการ' ไม่ได้ใช้MD5หรือSHA1ได้อีกต่อไป พวกเขามีขั้นตอนวิธีการคร่ำเครียดดี แต่ถือว่าเสียเพื่อความปลอดภัย

ปัจจุบันใช้cryptโดยที่ CRYPT_BLOWFISH เป็นแนวปฏิบัติที่ดีที่สุด
CRYPT_BLOWFISH ใน PHP เป็นการใช้งาน Bcrypt hash Bcrypt นั้นมาจากตัวเลขบล็อกของ Blowfish การใช้ประโยชน์จากการตั้งค่าคีย์ที่มีราคาแพงเพื่อลดขั้นตอนวิธี


29

ผู้ใช้ของคุณจะปลอดภัยมากขึ้นถ้าคุณใช้การสืบค้นแบบมีพารามิเตอร์แทนการเชื่อมงบ SQL และเกลือควรจะไม่ซ้ำกันสำหรับผู้ใช้แต่ละคนและควรเก็บไว้พร้อมกับแฮรหัสผ่าน


1
มีบทความที่ดีเกี่ยวกับความปลอดภัยใน PHP ที่ Nettuts + นอกจากนี้ยังมีการกล่าวถึงการใส่รหัสผ่านด้วย บางทีคุณควรดูที่: net.tutsplus.com/tutorials/php/ …
Fábio Antunes

3
Nettuts + เป็นบทความที่แย่มาก ๆ ที่จะใช้เป็นแบบจำลอง - รวมถึงการใช้ MD5 ซึ่งสามารถบังคับสัตว์เดรัจฉานได้ง่ายแม้กับเกลือ ให้ใช้ไลบรารี่ PHPass ซึ่งดีกว่าโค้ดใด ๆ ที่คุณอาจพบในเว็บไซต์กวดวิชาเช่นคำตอบนี้: stackoverflow.com/questions/1581610/…
RichVel

11

วิธีที่ดีกว่าสำหรับผู้ใช้แต่ละคนก็คือเกลือที่มีเอกลักษณ์

ประโยชน์ของการมีเกลือคือการทำให้ผู้โจมตียากที่จะสร้างลายเซ็น MD5 ของคำในพจนานุกรมทุกคำ แต่ถ้าผู้โจมตีรู้ว่าคุณมีเกลือคงที่พวกเขาสามารถสร้างลายเซ็น MD5 ล่วงหน้าของคำในพจนานุกรมทุกคำที่นำหน้าด้วยเกลือคงที่ของคุณ

วิธีที่ดีกว่าคือทุกครั้งที่ผู้ใช้เปลี่ยนรหัสผ่านระบบของคุณจะสร้างเกลือแบบสุ่มและเก็บเกลือนั้นพร้อมกับบันทึกผู้ใช้ มันทำให้การตรวจสอบรหัสผ่านมีราคาแพงกว่าเล็กน้อย (เนื่องจากคุณต้องมองหาเกลือก่อนที่คุณจะสามารถสร้างลายเซ็น MD5 ได้) แต่มันทำให้การโจมตีของ MD5 นั้นยากกว่ามาก


3
โดยปกติเกลือจะถูกเก็บไว้พร้อมกับแฮรหัสผ่าน (เช่นเอาท์พุทของcrypt()ฟังก์ชั่น) และเนื่องจากคุณต้องดึงแฮชของรหัสผ่านอยู่แล้วการใช้เกลือเฉพาะสำหรับผู้ใช้จะไม่ทำให้กระบวนการมีราคาแพงขึ้น (หรือคุณหมายถึงการสร้างเกลือสุ่มใหม่มีราคาแพงใช่ไหมฉันไม่คิดอย่างนั้น) มิฉะนั้น +1
Inshallah

เพื่อจุดประสงค์ด้านความปลอดภัยคุณอาจต้องการให้เข้าถึงตารางเท่านั้นผ่านขั้นตอนการจัดเก็บและป้องกันไม่ให้แฮชส่งคืน แต่ลูกค้าส่งผ่านสิ่งที่คิดว่าเป็นแฮชและรับค่าสถานะสำเร็จหรือล้มเหลว สิ่งนี้ทำให้ proc ที่เก็บไว้สามารถบันทึกความพยายามสร้างเซสชัน ฯลฯ
Steven Sudit

@Inshallah - หากผู้ใช้ทุกคนมีเกลือเดียวกันคุณสามารถใช้การโจมตีพจนานุกรมที่คุณใช้ใน user1 กับ user2 อีกครั้ง แต่ถ้าผู้ใช้แต่ละคนมีเกลือเฉพาะคุณจะต้องสร้างพจนานุกรมใหม่สำหรับผู้ใช้แต่ละคนที่คุณต้องการโจมตี
R Samuel Klatchko

@R ซามูเอล - นั่นเป็นเหตุผลว่าทำไมฉันถึงโหวตคำตอบของคุณเพราะแนะนำกลยุทธ์ที่ดีที่สุดเพื่อหลีกเลี่ยงการโจมตีดังกล่าว ความคิดเห็นของฉันหมายถึงการแสดงความงุนงงของฉันเกี่ยวกับสิ่งที่คุณพูดเกี่ยวกับค่าใช้จ่ายเพิ่มเติมของเกลือต่อผู้ใช้ซึ่งฉันไม่เข้าใจเลย (ตั้งแต่ "เกลือมักถูกเก็บไว้พร้อมกับรหัสผ่านแฮช" หน่วยเก็บข้อมูลเพิ่มเติมและข้อกำหนด CPU สำหรับเกลือต่อผู้ใช้หนึ่งคนนั้นเป็นกล้องจุลทรรศน์ดังนั้นพวกเขาจึงไม่จำเป็นต้องพูดถึง ... )
Inshallah

@Inshallah - ฉันกำลังคิดเกี่ยวกับกรณีที่คุณมีการตรวจสอบฐานข้อมูลว่ารหัสผ่านแฮชไม่เป็นไร (จากนั้นคุณมีการดึงหนึ่งฐานข้อมูลเพื่อรับเกลือและการเข้าถึงฐานสองเพื่อตรวจสอบรหัสผ่านที่แฮช) คุณถูกต้องเกี่ยวกับกรณีที่คุณดาวน์โหลดรหัสผ่านเกลือ / แฮชในการดึงข้อมูลเดียวแล้วทำการเปรียบเทียบกับลูกค้า ขอโทษสำหรับความสับสน.
R Samuel Klatchko

11

ด้วย PHP 5.5 (สิ่งที่ฉันอธิบายที่สามารถใช้ได้กับรุ่นแม้ก่อนหน้านี้ดูด้านล่าง) รอบมุมผมอยากจะแนะนำให้ใช้ของใหม่ที่สร้างขึ้นในการแก้ปัญหา: และpassword_hash() password_verify()มันมีหลายตัวเลือกเพื่อให้บรรลุระดับความปลอดภัยของรหัสผ่านที่คุณต้องการ (ตัวอย่างเช่นโดยการระบุพารามิเตอร์ "ต้นทุน" ผ่าน$optionsอาร์เรย์)

<?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));
?>

จะกลับมา

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

ดังที่คุณอาจเห็นสตริงประกอบด้วยเกลือรวมถึงต้นทุนที่ระบุไว้ในตัวเลือก นอกจากนี้ยังมีอัลกอริทึมที่ใช้

ดังนั้นเมื่อตรวจสอบรหัสผ่าน (เช่นเมื่อผู้ใช้เข้าสู่ระบบ) เมื่อใช้ฟรี password_verify()ฟังก์ชั่นมันจะแยกพารามิเตอร์ crypto ที่จำเป็นจากแฮชรหัสผ่านเอง

เมื่อไม่ระบุเกลือแฮชของรหัสผ่านที่สร้างขึ้นจะแตกต่างกันในทุกการโทร password_hash()เพราะเกลือจะถูกสร้างแบบสุ่ม ดังนั้นการเปรียบเทียบแฮชก่อนหน้านี้กับที่สร้างขึ้นใหม่จะล้มเหลวแม้จะเป็นรหัสผ่านที่ถูกต้องก็ตาม

การยืนยันผลงานเช่นนี้:

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.'));

ฉันหวังว่าการให้ฟังก์ชั่นในตัวเหล่านี้จะให้ความปลอดภัยของรหัสผ่านที่ดีขึ้นในไม่ช้าในกรณีที่มีการขโมยข้อมูลเนื่องจากจะช่วยลดจำนวนความคิดที่โปรแกรมเมอร์ต้องนำไปปฏิบัติ

มีห้องสมุดขนาดเล็ก (ไฟล์ PHP หนึ่งไฟล์) ที่จะให้ PHP 5.5 password_hashใน PHP 5.3.7+: https://github.com/ircmaxell/password_compat


2
ในกรณีส่วนใหญ่จะดีกว่าที่จะละเว้นพารามิเตอร์เกลือ ฟังก์ชั่นสร้างเกลือจากแหล่งสุ่มของระบบปฏิบัติการมีโอกาสน้อยมากที่คุณสามารถให้เกลือที่ดีกว่าด้วยตัวคุณเอง
martinstoeckli

1
นั่นคือสิ่งที่ฉันเขียนใช่มั้ย "ถ้าไม่มีการระบุเกลือมันจะถูกสร้างแบบสุ่มด้วยเหตุนี้จึงเป็นการดีกว่าที่จะไม่ระบุเกลือ"
akirk

ตัวอย่างส่วนใหญ่แสดงวิธีเพิ่มพารามิเตอร์ทั้งสองแม้ว่าจะไม่แนะนำให้ใส่เกลือดังนั้นฉันสงสัยว่าทำไม และเพื่อความซื่อสัตย์ฉันอ่านเฉพาะความคิดเห็นที่อยู่เบื้องหลังรหัสไม่ใช่ในบรรทัดถัดไป อย่างไรก็ตามมันจะดีกว่าไหมเมื่อตัวอย่างแสดงวิธีการใช้ฟังก์ชั่นให้ดีที่สุด?
martinstoeckli

คุณพูดถูกฉันเห็นด้วย ฉันได้เปลี่ยนคำตอบของฉันและแสดงความคิดเห็นออกมา ขอบคุณ
akirk

ฉันควรตรวจสอบว่ารหัสผ่านที่บันทึกไว้และรหัสผ่านที่ป้อนเหมือนกันหรือไม่ฉันกำลังใช้password_hash()และpassword_verifyไม่ว่ารหัสผ่านใด (ถูกต้องหรือไม่) ฉันใช้ฉันท้ายด้วยรหัสผ่านที่ถูกต้อง
Brownman Revival

0

นั่นโอเคสำหรับฉัน. Mr Atwood เขียนเกี่ยวกับความแข็งแกร่งของ MD5 เทียบกับโต๊ะสายรุ้งและโดยทั่วไปแล้วด้วยเกลือที่มีความยาวเช่นเดียวกับที่คุณกำลังนั่งสวย (แม้ว่าจะมีเครื่องหมายวรรคตอน / ตัวเลขสุ่มบางตัวก็สามารถปรับปรุงได้)

คุณสามารถดู SHA-1 ซึ่งดูเหมือนจะได้รับความนิยมมากขึ้นในวันนี้


6
หมายเหตุที่ด้านล่างของโพสต์ของนายแอทวู้ด (แดง) เชื่อมโยงไปยังโพสต์อื่นจากผู้ปฏิบัติงานด้านความปลอดภัยที่ระบุว่าใช้ MD5, SHA1 และแฮชที่รวดเร็วอื่น ๆ สำหรับการจัดเก็บรหัสผ่านผิดมาก
sipwiz

2
@ Matthew Scharley: ฉันไม่เห็นด้วยว่าความพยายามเพิ่มเติมที่กำหนดโดยอัลกอริทึมการแฮชรหัสผ่านที่มีราคาแพงคือการรักษาความปลอดภัยที่ผิดพลาด มันคือการป้องกันรหัสผ่านที่คาดเดาไม่ได้อย่างง่ายดาย หากคุณจำกัดความพยายามในการเข้าสู่ระบบคุณจะป้องกันสิ่งเดียวกัน (แม้ว่าจะมีประสิทธิภาพมากกว่าเล็กน้อย) แต่ถ้าฝ่ายตรงข้ามสามารถเข้าถึงฐานข้อมูลที่เก็บไว้ในแฮชได้เขาจะสามารถบังคับรหัสผ่าน (เดาได้ง่าย) เช่นนั้นได้อย่างรวดเร็วพอสมควร (ขึ้นอยู่กับการคาดเดาได้ง่าย) ค่าเริ่มต้นสำหรับอัลกอริทึมเข้ารหัส SHA-256 คือ 10,000 รอบดังนั้นจะทำให้ยากขึ้น 10,000 เท่า
Inshallah

3
แฮชแบบช้านั้นเกิดขึ้นจริง ๆ ด้วยการวนซ้ำแบบเร็วหนึ่งครั้งจำนวนมากและสับข้อมูลรอบ ๆ ระหว่างการวนซ้ำแต่ละครั้ง เป้าหมายคือเพื่อให้แน่ใจว่าแม้ว่าคนร้ายจะได้รับสำเนาของรหัสผ่านของคุณแฮ็กเขาจะต้องเผาซีพียูจำนวนมากเพื่อทดสอบพจนานุกรมของเขากับแฮชของคุณ
caf

4
@caf: ฉันเชื่อว่าอัลกอริทึม bcrypt ใช้ประโยชน์จากความสามารถในการกำหนดพารามิเตอร์ของ Eksblowfish ในการกำหนดตารางเวลาคีย์ ไม่แน่ใจว่าวิธีการนี้ทำงานอย่างไร แต่การกำหนดเวลาที่สำคัญมักเป็นการดำเนินการที่มีราคาแพงมากในช่วงที่วัตถุบริบทการเข้ารหัสเริ่มต้นก่อนที่จะทำการเข้ารหัสใด ๆ
Inshallah

3
Inshallah: นี่เป็นความจริง - อัลกอริธึมของ bcrypt เป็นการออกแบบที่แตกต่างกันโดยที่ crypto primitive นั้นเป็น cipher block แทนที่จะเป็นฟังก์ชัน hash ฉันหมายถึงแผนการตามฟังก์ชั่นแฮชเช่นเข้ารหัส MD5 ของ PHK ()
caf

0

ฉันต้องการเพิ่ม:

  • อย่า จำกัด รหัสผ่านของผู้ใช้ตามความยาว

สำหรับความเข้ากันได้กับระบบเก่ามักจะกำหนดขีดจำกัดความยาวสูงสุดของรหัสผ่าน นี่เป็นนโยบายความปลอดภัยที่ไม่ดี: หากคุณตั้งข้อ จำกัด ให้ตั้งค่าสำหรับความยาวขั้นต่ำของรหัสผ่านเท่านั้น

  • อย่าส่งรหัสผ่านของผู้ใช้ผ่านอีเมล

สำหรับการกู้คืนรหัสผ่านที่ลืมคุณควรส่งที่อยู่ซึ่งผู้ใช้สามารถเปลี่ยนรหัสผ่านได้

  • อัปเดตแฮชของรหัสผ่านผู้ใช้

แฮรหัสผ่านอาจล้าสมัย (พารามิเตอร์ของอัลกอริทึมอาจมีการปรับปรุง) โดยใช้ฟังก์ชั่นที่password_needs_rehash()คุณสามารถตรวจสอบได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.