- Hạn chế của MD5, SHA1 và các thuật toán băm cũ
- Mật khẩu nên được lưu theo dạng “slow hashing”
- PHP password_hash & password_verify – Chuẩn hiện đại
- Ví dụ sử dụng password_hash và password_verify (PHP)
- Ví dụ sử dụng Argon2id với password_hash
- Ví dụ chuyển từ MD5 sang password_hash
- Bcrypt – Lựa chọn ổn định và phổ biến
- Argon2 – Tiêu chuẩn mới, mạnh mẽ và bảo mật tối ưu
- Scrypt – Tối ưu chống tấn công phần cứng
- Bảng so sánh các phương pháp lưu trữ mật khẩu phổ biến
- Không bao giờ tự tạo salt hoặc tự code thuật toán
- Lựa chọn thuật toán nào cho hệ thống hiện nay?
- Kết luận
Hạn chế của MD5, SHA1 và các thuật toán băm cũ
MD5, SHA1 và SHA256 bản chất là các hàm băm nhanh, không có cơ chế chống tấn công brute-force hoặc rainbow table. Khi tin tặc có được hash, việc bẻ khóa trở nên quá dễ dàng nhờ GPU hoặc các dàn máy chuyên dụng. Vì vậy, các thuật toán này không còn được khuyến nghị dùng để lưu mật khẩu, chỉ nên dùng cho các mục đích kiểm tra toàn vẹn dữ liệu.
Mật khẩu nên được lưu theo dạng “slow hashing”
Slow hashing là kỹ thuật khiến việc băm mật khẩu trở nên chậm hơn có chủ đích, nhằm ngăn chặn brute-force tốc độ cao. Các thuật toán hiện đại như bcrypt, Argon2 và scrypt được thiết kế dựa trên triết lý này, giúp tăng độ an toàn đáng kể bằng cách tiêu tốn nhiều thời gian hoặc bộ nhớ cho mỗi lần băm.
PHP password_hash & password_verify – Chuẩn hiện đại
Hàm password_hash() tạo ra chuỗi băm an toàn với salt ngẫu nhiên tích hợp sẵn. Hàm password_verify() dùng để kiểm tra mật khẩu người dùng nhập có khớp với hash hay không, đảm bảo tính chính xác và an toàn tối đa. Đây là chuẩn bảo mật được khuyến nghị trong mọi hệ thống PHP hiện nay.
Ví dụ sử dụng password_hash và password_verify (PHP)
Dưới đây là ví dụ đơn giản cho quá trình đăng ký và đăng nhập sử dụng bcrypt qua hàm password_hash:
<?php
// Đăng ký tài khoản: lưu mật khẩu
$passwordPlain = $_POST['password'] ?? '';
// Tạo hash sử dụng bcrypt (mặc định)
$hash = password_hash($passwordPlain, PASSWORD_DEFAULT);
// Lưu $hash vào cột password trong database
// ví dụ: INSERT INTO users (username, password) VALUES (:username, :hash);
?>
Khi người dùng đăng nhập, bạn dùng password_verify để kiểm tra:
<?php
// Lấy mật khẩu người dùng nhập
$passwordInput = $_POST['password'] ?? '';
// Lấy hash đã lưu trong database theo username/email
// ví dụ: SELECT password FROM users WHERE username = :username
$hashFromDb = $user['password'] ?? '';
if (password_verify($passwordInput, $hashFromDb)) {
// Đăng nhập thành công
// Xử lý session, token...
} else {
// Sai mật khẩu
}
?>
Ví dụ sử dụng Argon2id với password_hash
Nếu server của bạn hỗ trợ Argon2, nên ưu tiên PASSWORD_ARGON2ID để có bảo mật tốt hơn:
<?php
$passwordPlain = $_POST['password'] ?? '';
$options = [
'memory_cost' => 1<<12, // 4096 KB
'time_cost' => 3, // số vòng lặp
'threads' => 2, // số luồng
];
$hash = password_hash($passwordPlain, PASSWORD_ARGON2ID, $options);
// Lưu $hash vào database như bình thường
?>
Xác thực vẫn sử dụng password_verify giống như với bcrypt, không cần thay đổi thêm gì:
<?php
if (password_verify($passwordInput, $hashFromDb)) {
// Mật khẩu đúng
}
?>
Ví dụ chuyển từ MD5 sang password_hash
Nếu hệ thống cũ đang lưu mật khẩu dưới dạng MD5, bạn có thể triển khai chiến lược nâng cấp dần khi người dùng đăng nhập:
<?php
$passwordInput = $_POST['password'] ?? '';
$user = getUserByUsername($username); // Lấy user từ DB
$hashFromDb = $user['password'] ?? '';
if (strlen($hashFromDb) === 32 && ctype_xdigit($hashFromDb)) {
// Có thể đây là MD5 cũ
if (md5($passwordInput) === $hashFromDb) {
// Đúng mật khẩu cũ: nâng cấp lên password_hash ngay
$newHash = password_hash($passwordInput, PASSWORD_DEFAULT);
updateUserPassword($user['id'], $newHash);
// Cho đăng nhập
} else {
// Sai mật khẩu
}
} else {
// Giả định là hash mới (bcrypt/argon2)
if (password_verify($passwordInput, $hashFromDb)) {
// Đăng nhập thành công
// Có thể cân nhắc password_needs_rehash để nâng cấp thêm nếu cần
if (password_needs_rehash($hashFromDb, PASSWORD_DEFAULT)) {
$newHash = password_hash($passwordInput, PASSWORD_DEFAULT);
updateUserPassword($user['id'], $newHash);
}
} else {
// Sai mật khẩu
}
}
?>
Bcrypt – Lựa chọn ổn định và phổ biến
Bcrypt là thuật toán slow-hash lâu đời nhưng cực kỳ đáng tin cậy. Nó tự động xử lý salt và cho phép cấu hình cost (độ phức tạp). Phần lớn CMS và framework sử dụng bcrypt như một giải pháp mặc định để lưu mật khẩu. Trong PHP, PASSWORD_DEFAULT hiện tại thường là bcrypt (tùy phiên bản), giúp bạn áp dụng dễ dàng mà không cần cấu hình phức tạp.
Argon2 – Tiêu chuẩn mới, mạnh mẽ và bảo mật tối ưu
Argon2 (đặc biệt là Argon2id) được xem là thuật toán lưu mật khẩu tốt trong các lựa chọn hiện nay. Nó chống tấn công GPU mạnh mẽ và cho phép điều chỉnh bộ nhớ, thời gian, số luồng xử lý. Nhiều hệ thống hiện đại đã chuyển sang dùng Argon2 để đạt mức bảo mật cao hơn, đặc biệt cho các ứng dụng cần lưu trữ mật khẩu lâu dài.
Scrypt – Tối ưu chống tấn công phần cứng
Scrypt được thiết kế để chống lại các hệ thống crack mật khẩu sử dụng phần cứng chuyên dụng như FPGA hay ASIC. Nhờ yêu cầu bộ nhớ cao, tin tặc khó thể brute-force hàng loạt hash cùng lúc. Scrypt thường được dùng trong một số thư viện, hệ thống xác thực và thậm chí trong một số cơ chế tiền mã hóa.
Bảng so sánh các phương pháp lưu trữ mật khẩu phổ biến
Bảng dưới đây giúp bạn hình dung nhanh ưu nhược điểm của từng phương pháp lưu trữ mật khẩu:
| Thuật toán | Loại | Tốc độ | Chống brute-force | Salt tích hợp | Mức khuyến nghị |
|---|---|---|---|---|---|
| MD5 | Hash nhanh | Rất nhanh | Rất kém | Không | Không dùng cho mật khẩu |
| SHA1/SHA256 | Hash nhanh | Rất nhanh | Kém | Không | Không khuyến nghị cho mật khẩu |
| Bcrypt | Slow hash | Chậm (có thể cấu hình) | Tốt | Có (ngầm định) | Khuyến nghị, phổ biến nhất |
| Argon2id | Slow hash, memory-hard | Chậm (có thể cấu hình) | Rất tốt | Có | Rất khuyến nghị nếu server hỗ trợ |
| Scrypt | Slow hash, memory-hard | Chậm (tốn RAM) | Rất tốt | Phụ thuộc implement | Tốt, nhưng ít phổ biến hơn bcrypt/Argon2 |
Không bao giờ tự tạo salt hoặc tự code thuật toán
Nhiều lập trình viên tự viết hàm băm, tự tạo salt hoặc xử lý thủ công. Đây là sai lầm phổ biến. Những hàm hiện đại như password_hash() đã xử lý toàn bộ phần này an toàn hơn rất nhiều, bao gồm cả salt và chi tiết thuật toán. Tự code dẫn đến vô số lỗ hổng khó lường, đặc biệt khi không am hiểu sâu về mật mã học.
Lựa chọn thuật toán nào cho hệ thống hiện nay?
Đối với PHP và đa số web app:
- Sử dụng password_hash() với PASSWORD_DEFAULT (thường là bcrypt) nếu bạn muốn sự ổn định, dễ cấu hình.
- Nếu server hỗ trợ, ưu tiên PASSWORD_ARGON2ID để có độ bảo mật cao hơn.
Đối với các ngôn ngữ khác:
- Ưu tiên dùng Argon2 hoặc bcrypt thông qua các thư viện uy tín.
- Tránh dùng MD5, SHA1, SHA256 cho việc lưu mật khẩu, kể cả khi có salt thủ công.
Kết luận
Bảo mật mật khẩu không còn là câu chuyện dùng MD5 hay SHA1 như thời 2010. Hệ thống hiện đại cần các phương pháp slow hashing như bcrypt, Argon2 hoặc scrypt để đảm bảo an toàn. Khi sử dụng password_hash và password_verify, bạn gần như đã tuân thủ đầy đủ các chuẩn bảo mật hiện đại nhất, đồng thời dễ dàng nâng cấp thuật toán trong tương lai mà không cần thay đổi logic ứng dụng quá nhiều.
Bình luận