Lưu mật khẩu đúng cách: băm, muối và vì sao không dùng MD5

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Một trong những lỗi bảo mật nghiêm trọng nhất mà người mới có thể mắc là lưu mật khẩu dạng văn bản thuần hoặc băm bằng thuật toán nhanh như MD5. Khi database bị lộ, kẻ tấn công sẽ có mật khẩu của người dùng, vốn thường được dùng lại ở nhiều dịch vụ khác. Bài này chỉ cách làm đúng.

Không lưu mật khẩu gốc, và cũng không mã hóa

Quy tắc đầu tiên: server không bao giờ cần biết mật khẩu gốc. Thay vì lưu mật khẩu, ta lưu giá trị băm (hash), kết quả của hàm một chiều không thể đảo ngược. Khi đăng nhập, ta băm mật khẩu người dùng nhập và so với giá trị đã lưu.

Mã hóa hai chiều (có thể giải mã) không phù hợp, vì ai có khóa là lấy lại được mật khẩu thật. Với mật khẩu, hãy dùng băm.

Vì sao MD5 và SHA-256 trần là chưa đủ?

MD5, SHA-1, SHA-256 được thiết kế để nhanh. Với kẻ tấn công, nhanh là lợi thế: một card đồ họa có thể thử một lượng khổng lồ mật khẩu mỗi giây. Chúng còn bị tấn công bằng bảng băm dựng sẵn (rainbow table) nếu không có muối. Mật khẩu cần một hàm băm cố ý chậm và tốn bộ nhớ.

Muối (salt)

Muối là chuỗi ngẫu nhiên duy nhất cho mỗi mật khẩu, được ghép vào trước khi băm. Nhờ vậy hai người dùng cùng mật khẩu vẫn có hai giá trị băm khác nhau, và bảng băm dựng sẵn mất tác dụng. Muối không cần giữ bí mật; các thuật toán hiện đại tự sinh và lưu nó cùng kết quả băm trong một chuỗi.

Các thuật toán nên dùng

  • Argon2id: thuật toán hiện đại, được khuyến nghị rộng rãi, chống tấn công bằng GPU nhờ tốn bộ nhớ.
  • bcrypt: già dặn, được hỗ trợ khắp nơi, có tham số chi phí điều chỉnh độ chậm.
  • scrypt, PBKDF2: lựa chọn khác, đặc biệt PBKDF2 khi cần tuân thủ một số tiêu chuẩn.

Hãy dùng chức năng có sẵn trong ngôn ngữ hoặc framework thay vì tự cài đặt.

Ví dụ trong vài ngôn ngữ

// PHP$hash = password_hash($password, PASSWORD_DEFAULT);if (password_verify($input, $hash)) { /* đúng */ }if (password_needs_rehash($hash, PASSWORD_DEFAULT)) { /* băm lại */ }// Laravel$hash = Hash::make($password);Hash::check($input, $hash);
# Python (thư viện bcrypt)import bcrypthashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))ok = bcrypt.checkpw(input.encode(), hashed)
// Node.js (thư viện bcrypt)const bcrypt = require('bcrypt');const hash = await bcrypt.hash(password, 12);const ok = await bcrypt.compare(input, hash);

Hàm kiểm tra so sánh theo thời gian không đổi, tránh lộ thông tin qua độ trễ. Đừng tự so sánh hai chuỗi băm bằng toán tử thông thường.

Những việc cần làm thêm

  • Chính sách mật khẩu hợp lý: ưu tiên độ dài hơn các quy tắc ký tự phức tạp; chặn các mật khẩu phổ biến đã bị lộ.
  • Giới hạn số lần thử: làm chậm hoặc khóa tạm khi nhập sai nhiều lần để chống dò mật khẩu.
  • Xác thực hai yếu tố: bảo vệ cả khi mật khẩu bị lộ.
  • Quy trình đặt lại mật khẩu an toàn: token ngẫu nhiên, dùng một lần, có hạn ngắn; không gửi mật khẩu qua email.
  • Nâng cấp dần: khi người dùng đăng nhập thành công, băm lại bằng tham số mạnh hơn nếu cần.
  • Không ghi log mật khẩu: kiểm tra cả log lỗi và công cụ giám sát.

Đã lỡ lưu sai thì sao?

Nếu hệ thống cũ lưu MD5, hãy chuyển dần: khi người dùng đăng nhập, kiểm tra bằng cách cũ rồi lập tức băm lại bằng thuật toán mạnh và lưu thay thế. Với tài khoản lâu không đăng nhập, cân nhắc yêu cầu đặt lại mật khẩu. Nếu nghi ngờ dữ liệu đã lộ, thông báo cho người dùng và buộc đổi mật khẩu.

Chọn tham số chi phí hợp lý

Độ chậm của hàm băm là thứ bạn điều chỉnh được. Với bcrypt đó là số vòng (cost), với Argon2 là bộ nhớ, số lần lặp và độ song song. Mục tiêu là mỗi lần băm mất khoảng vài chục đến vài trăm mili giây trên server của bạn: đủ chậm để kẻ tấn công thử hàng tỷ mật khẩu là bất khả thi, nhưng đủ nhanh để đăng nhập không bị cảm nhận là chậm. Hãy đo trên phần cứng thật và xem lại tham số định kỳ vì máy móc ngày càng mạnh. Nhờ tính năng băm lại khi đăng nhập, bạn có thể nâng chi phí dần dần mà không làm phiền người dùng.

Kết luận

Tóm lại: băm bằng Argon2id hoặc bcrypt, dùng muối riêng cho từng mật khẩu (thư viện tự lo), kiểm tra bằng hàm có sẵn và bổ sung giới hạn thử và xác thực hai yếu tố. Chỉ vài dòng code đúng cách có thể bảo vệ hàng nghìn người dùng của bạn.

Muốn học bài bản và có lộ trình rõ ràng? Bạn có thể xem các khóa học trên codebem.com.

author_photo
Nguyễn Đình Cường

0 comment