nicetool.dev logo
#security#hashing#backend

MD5, SHA-256, bcrypt hay Argon2: Băm mật khẩu thế nào cho đúng?

Vì sao hàm băm nhanh như MD5, SHA-256 không dùng để lưu mật khẩu, salt và hệ số chi phí bảo vệ người dùng ra sao, và nên chọn Argon2id, bcrypt, scrypt hay PBKDF2 vào năm 2026.

5 phút đọc

"Cứ băm mật khẩu bằng SHA-256 là được" nghe rất hợp lý, nhưng đây lại là một trong những lỗi bảo mật phổ biến nhất trong ứng dụng web. Không phải hàm băm nào cũng được thiết kế cho cùng một mục đích. Bài viết này giải thích sự khác nhau giữa hàm băm đa dụng (MD5, SHA-1, SHA-256) và hàm băm mật khẩu (bcrypt, scrypt, Argon2), và bạn nên dùng gì hiện nay.

Hàm băm làm gì

Hàm băm mật mã biến mọi dữ liệu đầu vào thành một "dấu vân tay" có độ dài cố định:

SHA-256("hello")  = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello!") = ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b

Một hàm băm tốt có ba tính chất:

  1. Tất định - cùng đầu vào luôn cho cùng kết quả.
  2. Một chiều - không thể tính ngược đầu vào từ kết quả.
  3. Hiệu ứng thác đổ - đổi một ký tự là kết quả khác hoàn toàn.

Bạn có thể tự kiểm chứng cả ba bằng công cụ Tạo mã Hash: gõ một từ, sau đó đổi một chữ cái và so sánh kết quả.

Hàm băm đa dụng: MD5, SHA-1, SHA-256

Các thuật toán này được thiết kế để chạy nhanh. Đó chính là điều bạn cần khi kiểm tra tệp tải về dung lượng 4 GB hay ký hàng triệu request API.

  • MD5 (128 bit) đã bị phá về mặt bảo mật: tấn công va chạm thực tế có từ năm 2004. Nó vẫn dùng được làm checksum không liên quan bảo mật, ví dụ phát hiện tệp bị hỏng.
  • SHA-1 (160 bit) cũng đã bị phá về va chạm (tấn công "SHAttered" công bố năm 2017). Không nên dùng trong thiết kế mới.
  • SHA-256 / SHA-512 (họ SHA-2) vẫn an toàn, dùng rộng rãi cho checksum, chữ ký số, HMAC và blockchain.

Vì sao "nhanh" lại tệ với mật khẩu

Mật khẩu thường ngắn và dễ đoán. Nếu kẻ tấn công lấy được cơ sở dữ liệu, họ không cần "giải ngược" hash: họ chỉ việc đoán hàng tỷ mật khẩu phổ biến, băm từng cái rồi so sánh.

Một GPU hiện đại có thể tính hàng tỷ hash SHA-256 mỗi giây. Mật khẩu 8 ký tự gồm chữ thường và số có khoảng 2,8 × 10¹² tổ hợp, có thể thử hết trong chưa tới một giờ. Các bộ từ điển mật khẩu bị lộ chứa hàng trăm triệu mật khẩu thật còn khiến việc này nhanh hơn nữa.

Vậy vấn đề của sha256(password) không phải SHA-256 yếu, mà là SHA-256 quá nhanh.

Salt: chặn tấn công tính sẵn

Salt là một giá trị ngẫu nhiên (thường 16 byte) được tạo riêng cho từng người dùng và lưu cạnh hash:

hash = H(salt + password)

Salt đảm bảo hai người cùng đặt mật khẩu 123456 sẽ có hash khác nhau, và vô hiệu hóa các "bảng cầu vồng" (rainbow table) tính sẵn. Nhưng salt không làm chậm việc đoán mật khẩu của một tài khoản. Muốn vậy cần có hệ số chi phí.

Hàm băm dành riêng cho mật khẩu

Các hàm băm mật khẩu được thiết kế chậm có chủ đích và cấu hình được:

Thuật toán Năm Điều chỉnh chi phí Tốn bộ nhớ Ghi chú
PBKDF2 2000 Số vòng lặp Không Có sẵn trên hầu hết nền tảng, đạt chuẩn FIPS
bcrypt 1999 Cost factor (2^cost vòng) Một chút Hỗ trợ rất rộng rãi, giới hạn đầu vào 72 byte
scrypt 2009 CPU + bộ nhớ Có Được một số tiền mã hóa sử dụng
Argon2id 2015 Thời gian, bộ nhớ, song song Có Chiến thắng cuộc thi Password Hashing Competition

"Tốn bộ nhớ" (memory-hard) nghĩa là mỗi lần đoán cần một lượng RAM đáng kể. GPU và chip chuyên dụng có hàng nghìn lõi nhưng mỗi lõi ít bộ nhớ, nên thuật toán memory-hard triệt tiêu phần lớn lợi thế của chúng.

Cấu hình khuyến nghị

OWASP Password Storage Cheat Sheet hiện khuyến nghị theo thứ tự ưu tiên:

  1. Argon2id với tối thiểu 19 MiB bộ nhớ, 2 vòng lặp và độ song song 1.
  2. scrypt với N = 2^17, r = 8, p = 1 nếu không có Argon2id.
  3. bcrypt với cost factor từ 10 trở lên cho hệ thống cũ.
  4. PBKDF2-HMAC-SHA256 với 600.000 vòng lặp khi bắt buộc tuân thủ FIPS.

Quy tắc dễ nhớ: chỉnh chi phí sao cho mỗi lần băm mất khoảng 100-500 ms trên máy chủ, và tăng dần sau vài năm khi phần cứng mạnh lên.

Ví dụ với Node.js

import argon2 from 'argon2';

// Khi đăng ký
const hash = await argon2.hash(password, { type: argon2.argon2id });
// Lưu `hash` - chuỗi này đã chứa thuật toán, tham số và salt:
// $argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHQ$...

// Khi đăng nhập
const ok = await argon2.verify(hash, passwordFromUser);

Với bcrypt gần như tương tự:

import bcrypt from 'bcrypt';

const hash = await bcrypt.hash(password, 12); // cost factor 12
const ok = await bcrypt.compare(passwordFromUser, hash);

Để ý rằng bạn không cần tự quản lý salt: thư viện hiện đại tự tạo và nhúng salt vào chuỗi kết quả.

Những lỗi thường gặp

  • Dùng thẳng MD5 hoặc SHA-x cho mật khẩu, kể cả khi có salt.
  • Tự chế thuật toán, kiểu sha256(sha256(password) + secret). Hãy dùng thư viện đã được kiểm chứng.
  • So sánh hash bằng ==. Hãy dùng hàm verify của thư viện, vốn so sánh trong thời gian hằng số.
  • Giới hạn độ dài mật khẩu quá ngắn. Cho phép cụm mật khẩu dài. Lưu ý bcrypt chỉ dùng 72 byte đầu tiên.
  • Không bao giờ nâng cấp. Khi người dùng đăng nhập thành công, bạn có thể băm lại mật khẩu với tham số mạnh hơn.

Khi nào MD5 và SHA-256 vẫn là công cụ đúng

  • Kiểm tra toàn vẹn tệp: công bố checksum SHA-256 của bản phát hành để người dùng kiểm tra tệp tải về.
  • HMAC: ký webhook và request API bằng HMAC-SHA256(secret, body). Công cụ Tạo mã Hash có thể tính chữ ký HMAC giúp bạn gỡ lỗi khi tích hợp.
  • Khóa cache và loại trùng: băm nội dung tệp để phát hiện tệp trùng lặp.
  • Định danh theo nội dung: Git, Docker image và nhiều hệ thống lưu trữ nhận diện dữ liệu bằng hash.

Kết luận

Dùng hàm băm nhanh (SHA-256) cho kiểm tra toàn vẹn và chữ ký, còn hàm chậm, có salt, tốn bộ nhớ (Argon2id hoặc bcrypt) cho mật khẩu. Không bao giờ lưu mật khẩu bằng MD5 hay SHA-256 thuần, và hãy để một thư viện được duy trì tốt xử lý salt cùng tham số giúp bạn.

Tác giả

Nguyễn Thành Nam

Full-Stack Engineer · Backend & Platform

Kỹ sư full-stack với hơn 9 năm kinh nghiệm xây dựng API và nền tảng lưu lượng lớn bằng Go, NestJS, PHP (Laravel, Phalcon) và Node.js. Chuyên về thiết kế API GraphQL, hệ thống thời gian thực và tối ưu hiệu năng; xây dựng NICETOOL.dev cho cộng đồng lập trình viên.

Tự thử ngay

Bài viết liên quan