Tìm hiểu cơ chế mã hóa mật khẩu bằng hàm băm Bcrypt
Bcrypt là hàm băm mật khẩu được thiết kế để chậm có chủ đích, kết hợp muối (salt) ngẫu nhiên và hệ số chi phí (cost factor) có thể điều chỉnh. Nhờ đó, ngay cả khi cơ sở dữ liệu bị lộ, kẻ tấn công phải tốn rất nhiều thời gian để đoán từng mật khẩu, và hai người dùng cùng mật khẩu vẫn có chuỗi băm khác nhau. Gọi là "mã hóa mật khẩu" cho quen, nhưng chính xác thì bcrypt là băm: một chiều, không giải mã ngược được.
Vì sao không lưu mật khẩu dạng thường hay băm nhanh
Lưu mật khẩu dạng văn bản thường thì một lần lộ cơ sở dữ liệu là lộ toàn bộ. Mã hóa hai chiều cũng không ổn vì khóa giải mã thường nằm cùng hệ thống. Giải pháp đúng là băm: lưu kết quả của hàm một chiều, khi đăng nhập thì băm mật khẩu nhập vào rồi so sánh.
Nhưng không phải hàm băm nào cũng phù hợp. MD5, SHA-1, SHA-256 được thiết kế để nhanh, phục vụ kiểm tra toàn vẹn dữ liệu. Với phần cứng đồ họa hiện đại, kẻ tấn công có thể thử một lượng khổng lồ mật khẩu mỗi giây với các hàm này. Mật khẩu ngắn hoặc phổ biến sẽ bị tìm ra rất nhanh.
| Cách lưu | Vấn đề |
|---|---|
| Văn bản thường | Lộ ngay toàn bộ |
| Mã hóa hai chiều | Lộ khóa là lộ tất cả |
| MD5/SHA không muối | Tra bảng băm có sẵn, mật khẩu giống nhau có băm giống nhau |
| SHA có muối | Tốt hơn nhưng vẫn quá nhanh, dễ dò bằng GPU |
| bcrypt, scrypt, Argon2, PBKDF2 | Chậm có chủ đích, có muối, phù hợp cho mật khẩu |
Bcrypt hoạt động như thế nào
Bcrypt được công bố năm 1999, dựa trên thuật toán mã hóa Blowfish. Điểm then chốt là giai đoạn thiết lập khóa tốn kém (gọi là EksBlowfish), được lặp lại 2 mũ cost lần. Quá trình tổng quát:
- Sinh muối ngẫu nhiên 16 byte cho mỗi mật khẩu.
- Dùng mật khẩu và muối để thiết lập trạng thái nội bộ của Blowfish, lặp lại số vòng theo hệ số chi phí.
- Dùng trạng thái đó mã hóa một chuỗi cố định nhiều lần để ra kết quả băm.
- Ghép phiên bản thuật toán, hệ số chi phí, muối và kết quả thành một chuỗi duy nhất để lưu.
Đọc chuỗi băm bcrypt
Một chuỗi bcrypt có dạng bắt đầu bằng $2b$, tiếp theo là hệ số chi phí hai chữ số (ví dụ 12), rồi 22 ký tự muối và 31 ký tự kết quả băm, mã hóa theo biến thể base64 riêng. Vì muối và chi phí nằm ngay trong chuỗi, bạn chỉ cần lưu một trường trong cơ sở dữ liệu; thư viện tự đọc các thông số khi xác minh.
- $2b$: phiên bản thuật toán. Các tiền tố $2a$, $2y$ là biến thể cũ hoặc theo cách triển khai.
- Hệ số chi phí: mỗi lần tăng 1, thời gian tính gấp đôi.
- Muối: bảo đảm hai mật khẩu giống nhau cho kết quả khác nhau, vô hiệu hóa bảng băm tính sẵn.
Chọn hệ số chi phí
Hệ số chi phí là sự cân bằng giữa an toàn và hiệu năng. Mục tiêu thường được khuyến nghị là để mỗi lần băm mất một khoảng thời gian chấp nhận được trên máy chủ của bạn, đủ chậm với kẻ tấn công nhưng không làm đăng nhập ì ạch. Hướng dẫn của OWASP đặt mức tối thiểu là 10. Cách làm thực tế: đo thời gian băm trên phần cứng sản xuất, chọn giá trị phù hợp, và định kỳ tăng lên khi phần cứng mạnh hơn.
Khi tăng hệ số chi phí, bạn không cần ép người dùng đổi mật khẩu: ở lần đăng nhập thành công tiếp theo, kiểm tra hệ số trong chuỗi băm cũ, nếu thấp hơn mức hiện tại thì băm lại mật khẩu vừa nhập với hệ số mới và cập nhật.
Những giới hạn cần biết
- Giới hạn 72 byte: bcrypt chỉ dùng tối đa 72 byte đầu của mật khẩu. Với tiếng Việt có dấu mã hóa UTF-8, mỗi ký tự có thể chiếm nhiều byte. Nếu cho phép mật khẩu rất dài, cân nhắc giới hạn độ dài hợp lý hoặc dùng thuật toán khác. Cách băm trước bằng SHA rồi đưa vào bcrypt cần làm cẩn thận (mã hóa base64 kết quả) để tránh lỗi byte rỗng.
- Không dùng nhiều bộ nhớ: bcrypt chậm về tính toán nhưng ít tốn bộ nhớ, nên phần cứng chuyên dụng vẫn có lợi thế nhất định.
- Tấn công từ chối dịch vụ: vì băm chậm, endpoint đăng nhập cần giới hạn tần suất để không bị lợi dụng làm quá tải máy chủ.
Bcrypt so với Argon2, scrypt, PBKDF2
| Thuật toán | Đặc điểm |
|---|---|
| Argon2id | Thắng cuộc thi Password Hashing Competition, tốn cả CPU và bộ nhớ; được OWASP ưu tiên cho hệ thống mới |
| scrypt | Tốn bộ nhớ, chống phần cứng chuyên dụng tốt |
| bcrypt | Trưởng thành, hỗ trợ rộng rãi, vẫn là lựa chọn tốt khi Argon2 không sẵn có |
| PBKDF2 | Đáp ứng một số yêu cầu tiêu chuẩn chính phủ, cần số vòng lặp rất cao |
Với hệ thống đang chạy bcrypt cấu hình đúng, không có lý do cấp bách để chuyển đổi. Với dự án mới, Argon2id là lựa chọn mặc định nên cân nhắc.
Dùng bcrypt đúng trong ứng dụng
- Dùng thư viện bcrypt chuẩn của ngôn ngữ, không tự triển khai.
- Dùng hàm xác minh của thư viện thay vì tự so sánh chuỗi, để tránh lỗi và tấn công dựa trên thời gian.
- Không ghi mật khẩu thô vào log, kể cả log lỗi.
- Kết hợp kiểm tra mật khẩu mới với danh sách mật khẩu đã lộ.
- Giới hạn số lần đăng nhập sai và hỗ trợ xác thực đa yếu tố.
Băm mật khẩu đúng cách giảm thiệt hại khi cơ sở dữ liệu bị lộ, ví dụ qua lỗ hổng SQL Injection, nhưng không ngăn được mật khẩu bị đánh cắp ngay ở máy người dùng, như với phần mềm ghi bàn phím. Đây là lý do xác thực đa lớp vẫn cần thiết. Nhóm lỗi lưu trữ mật khẩu yếu thuộc mục lỗi mật mã trong OWASP Top 10.
Câu hỏi thường gặp
Bcrypt có giải mã ngược được không?
Không. Bcrypt là hàm băm một chiều. Cách duy nhất để tìm mật khẩu là đoán và băm thử từng ứng viên, việc mà hệ số chi phí khiến trở nên rất tốn kém.
Có cần tự tạo muối khi dùng bcrypt?
Không. Thư viện bcrypt tự sinh muối ngẫu nhiên và nhúng vào chuỗi kết quả. Bạn chỉ cần lưu chuỗi đó và dùng hàm xác minh của thư viện.
Nên chuyển từ MD5 sang bcrypt thế nào khi đã có người dùng?
Một cách phổ biến là băm lại giá trị MD5 cũ bằng bcrypt cho toàn bộ tài khoản ngay lập tức, rồi khi người dùng đăng nhập thì thay bằng bcrypt của mật khẩu gốc. Như vậy không còn MD5 trần trong cơ sở dữ liệu.
Mật khẩu dài hơn 72 byte có an toàn với bcrypt không?
Phần vượt quá 72 byte bị bỏ qua, nên hai mật khẩu chung 72 byte đầu sẽ được coi là giống nhau. Với đa số người dùng điều này không đáng kể, nhưng hệ thống cho phép mật khẩu rất dài nên giới hạn độ dài hoặc dùng Argon2.
Bài liên quan

Bảo mật API trong môi trường phát triển ứng dụng Web
Các nguyên tắc bảo mật API cho lập trình viên web: xác thực, phân quyền theo đối tượng, kiểm tra đầu vào, giới hạn tần suất, quản lý khóa, nhật ký và kiểm thử.

Bảo mật tệp tin cấu hình nhạy cảm tránh bị lộ lọt
Cách bảo vệ tệp .env, config, khóa API, mật khẩu cơ sở dữ liệu: không đưa vào Git, chặn truy cập web, phân quyền tệp, dùng trình quản lý bí mật và xử lý khi lộ.

Các bước cơ bản để rà soát lỗ hổng bảo mật ứng dụng
Quy trình rà soát lỗ hổng ứng dụng web: xác định phạm vi, lập danh mục tài sản, quét tự động, rà soát mã, kiểm thử thủ công và theo dõi khắc phục.

Các chương trình tìm lỗi nhận thưởng Bug Bounty và lưu ý
Bug Bounty hoạt động thế nào, các dạng chương trình, quy tắc phạm vi và safe harbor, cách viết báo cáo tốt và lưu ý pháp lý cho người mới.