Password Hashing: การใช้งาน password_hash() เบื้องต้น
ในบทเรียนก่อนหน้า เราได้เรียนรู้เกี่ยวกับการป้องกัน CSRF (Cross-Site Request Forgery) ซึ่งเป็นส่วนสำคัญในการรักษาความปลอดภัยของเว็บแอปพลิเคชัน ในบทนี้ เราจะมาเรียนรู้เกี่ยวกับการเก็บรหัสผ่านของผู้ใช้ให้ปลอดภัย โดยใช้ฟังก์ชัน password_hash() ของ PHP ซึ่งเป็นวิธีที่แนะนำในการเก็บรหัสผ่านในปัจจุบัน
การเก็บรหัสผ่านแบบ plaintext (ข้อความธรรมดา) เป็นสิ่งที่อันตรายอย่างยิ่ง หากฐานข้อมูลถูกแฮกเกอร์เข้าถึงได้ รหัสผ่านทั้งหมดของผู้ใช้จะถูกเปิดเผยทันที ดังนั้น เราจึงต้องแปลงรหัสผ่านให้เป็นรูปแบบที่ไม่สามารถอ่านได้ง่ายๆ หรือที่เรียกว่า "hash" ซึ่ง password_hash() ช่วยให้เราทำสิ่งนี้ได้อย่างง่ายดายและปลอดภัย
ทำไมต้อง Password Hashing?
Password Hashing คือกระบวนการแปลงรหัสผ่านให้เป็นสายอักขระที่ซับซ้อนและไม่สามารถย้อนกลับได้ (one-way function) ซึ่งหมายความว่า เราสามารถตรวจสอบได้ว่ารหัสผ่านที่ผู้ใช้ป้อนนั้นถูกต้องหรือไม่ โดยไม่ต้องเก็บรหัสผ่านจริงไว้ในฐานข้อมูล
วิธีการ hashing แบบเก่า เช่น MD5 หรือ SHA1 ไม่ปลอดภัยอีกต่อไป เนื่องจากมีเครื่องมือที่สามารถ "crack" หรือถอดรหัส hash เหล่านี้ได้ง่าย password_hash() ใช้ algorithm ที่แข็งแกร่งกว่า เช่น bcrypt (ค่าเริ่มต้น) หรือ Argon2i ซึ่งออกแบบมาเพื่อป้องกันการโจมตีด้วย brute-force และ rainbow table
การใช้งาน password_hash()
ฟังก์ชัน password_hash() รับพารามิเตอร์สองตัว:
- รหัสผ่าน: รหัสผ่านที่ต้องการ hash (string)
- Algorithm: Algorithm ที่ใช้ในการ hashing (integer หรือ string)
- Options (optional): อาร์เรย์ของตัวเลือกเพิ่มเติม เช่น
costสำหรับ bcrypt หรือmemory_cost,time_cost, และthreadsสำหรับ Argon2i
ตัวอย่าง:
<?php
$password = "mySecretPassword123";
// ใช้ bcrypt algorithm (ค่าเริ่มต้น)
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
echo "Hashed password: " . $hashedPassword . "<br>";
// ใช้ Argon2i algorithm (ต้องใช้ PHP 7.2 ขึ้นไป)
// $options = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 2];
// $hashedPassword = password_hash($password, PASSWORD_ARGON2I, $options);
// echo "Hashed password (Argon2i): " . $hashedPassword . "<br>";
?>
คำอธิบาย:
PASSWORD_DEFAULTคือค่าคงที่ที่กำหนด algorithm ที่แนะนำให้ใช้ (ปัจจุบันคือ bcrypt) การใช้PASSWORD_DEFAULTทำให้โค้ดของคุณมีความยืดหยุ่นมากขึ้น เพราะ PHP อาจเปลี่ยน algorithm ที่แนะนำในอนาคต และโค้ดของคุณจะยังคงทำงานได้โดยไม่ต้องแก้ไข- การใช้ Argon2i (
PASSWORD_ARGON2I) เหมาะสำหรับระบบที่ต้องการความปลอดภัยสูงเป็นพิเศษ แต่ต้องใช้ PHP 7.2 ขึ้นไป และอาจต้องปรับค่าmemory_cost,time_cost, และthreadsให้เหมาะสมกับทรัพยากรของ server
การเลือก Algorithm
การเลือก algorithm ที่เหมาะสมขึ้นอยู่กับความต้องการด้านความปลอดภัยและประสิทธิภาพของระบบของคุณ
- bcrypt (
PASSWORD_DEFAULT): เป็น algorithm ที่แนะนำให้ใช้โดยทั่วไป มีความปลอดภัยสูงและทำงานได้ดีบน server ส่วนใหญ่ - Argon2i (
PASSWORD_ARGON2I): เป็น algorithm ที่ทันสมัยกว่า bcrypt และมีความต้านทานต่อการโจมตีด้วย hardware ที่เฉพาะเจาะจง (เช่น GPU) มากกว่า แต่ต้องการทรัพยากรมากกว่าและอาจไม่พร้อมใช้งานบน server ทุกเครื่อง
หากคุณไม่แน่ใจว่าจะเลือก algorithm ใด ให้ใช้ PASSWORD_DEFAULT ซึ่งเป็นตัวเลือกที่ปลอดภัยและยืดหยุ่นที่สุด
การสร้าง Salt
password_hash() จะสร้าง "salt" โดยอัตโนมัติ ซึ่งเป็นข้อมูลสุ่มที่ใช้ในการเพิ่มความซับซ้อนให้กับ hash Salt ช่วยป้องกันการโจมตีด้วย rainbow table ซึ่งเป็นตารางที่เก็บ hash ของรหัสผ่านที่พบบ่อย
สำคัญ: คุณไม่จำเป็นต้องสร้าง salt เอง password_hash() จะจัดการเรื่องนี้ให้คุณโดยอัตโนมัติ
การเก็บ Hash ในฐานข้อมูล
เมื่อคุณได้ hash ของรหัสผ่านแล้ว คุณจะต้องเก็บ hash นี้ไว้ในฐานข้อมูล เพื่อใช้ในการตรวจสอบรหัสผ่านของผู้ใช้ในภายหลัง
ข้อควรระวัง:
- ขนาดของ field: hash ที่สร้างโดย
password_hash()อาจมีความยาว ดังนั้น ตรวจสอบให้แน่ใจว่า field ในฐานข้อมูลของคุณมีขนาดเพียงพอที่จะเก็บ hash ได้ โดยทั่วไป field ที่มีขนาด 255 ตัวอักษรขึ้นไปจะเพียงพอ - ประเภทของ field: ใช้ประเภทข้อมูล
VARCHARหรือTEXTสำหรับ field ที่เก็บ hash
ตัวอย่าง (MySQL):
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL
);
ตัวอย่างการ insert ข้อมูลด้วย PDO:
<?php
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydatabase", "username", "password");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$username = "john.doe";
$password = "mySecretPassword123";
$email = "john.doe@example.com";
// Hash รหัสผ่าน
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// เตรียม prepared statement
$stmt = $pdo->prepare("INSERT INTO users (username, password, email) VALUES (:username, :password, :email)");
// Bind parameters
$stmt->bindParam(":username", $username);
$stmt->bindParam(":password", $hashedPassword);
$stmt->bindParam(":email", $email);
// Execute statement
$stmt->execute();
echo "User added successfully!";
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
?>
ข้อผิดพลาดที่พบบ่อย
- ลืม hash รหัสผ่าน: ข้อผิดพลาดที่พบบ่อยที่สุดคือการพยายามเก็บรหัสผ่านแบบ plaintext ในฐานข้อมูล
- ขนาด field ไม่เพียงพอ: ตรวจสอบให้แน่ใจว่า field ที่เก็บ hash มีขนาดเพียงพอ (อย่างน้อย 255 ตัวอักษร)
- ใช้ algorithm ที่ไม่ปลอดภัย: หลีกเลี่ยงการใช้ MD5 หรือ SHA1 และเลือกใช้ bcrypt หรือ Argon2i แทน
- ไม่จัดการ exception: ควรใช้ try-catch block เพื่อจัดการ exception ที่อาจเกิดขึ้นระหว่างการเชื่อมต่อฐานข้อมูลและการ execute query
สรุป
ในบทนี้ เราได้เรียนรู้เกี่ยวกับการใช้ password_hash() เพื่อเก็บรหัสผ่านของผู้ใช้ให้ปลอดภัย การ hashing รหัสผ่านเป็นสิ่งสำคัญอย่างยิ่งในการปกป้องข้อมูลของผู้ใช้ และ password_hash() เป็นเครื่องมือที่ใช้งานง่ายและมีประสิทธิภาพในการทำสิ่งนี้ ในบทถัดไป เราจะมาเรียนรู้วิธีการตรวจสอบรหัสผ่านที่ผู้ใช้ป้อน โดยใช้ password_verify()