Password Hashing – 5 thuật toán băm hiện đại giúp bảo vệ mật khẩu an toàn cho hệ thống

0 0 0

Người đăng: Nam Phạm

Theo Viblo Asia

Password Hashing – Bảo vệ mật khẩu an toàn cho hệ thống hiện đại

1. Tại sao cần băm (hash) mật khẩu?

Khi người dùng đăng ký hoặc sử dụng dịch vụ, họ thường cung cấp mật khẩu để xác thực. Nếu hệ thống lưu trữ mật khẩu dạng “thô” (plain text), chỉ cần dữ liệu bị rò rỉ là hacker có thể chiếm đoạt tài khoản. Để tránh điều này, mật khẩu cần được băm (hash) trước khi lưu trữ.

  • Băm mật khẩu là quá trình chuyển đổi mật khẩu gốc thành một chuỗi ký tự khó đoán, không thể đảo ngược về mật khẩu ban đầu.
  • Nếu lưu mật khẩu dạng “thô” (plain text), khi dữ liệu bị rò rỉ, hacker dễ dàng chiếm đoạt tài khoản người dùng.
  • Ví dụ nguy hiểm:
    • Mật khẩu gốc: 123456
    • Sau khi băm (MD5 - KHÔNG an toàn): e10adc3949ba59abbe56e057f20f883e
  • Lợi ích của băm mật khẩu:
    • Dữ liệu rò rỉ không thể bị dùng để đăng nhập ngay lập tức.
    • Kể cả quản trị viên cũng không biết mật khẩu thật của người dùng.

Password leak

2. Tiêu chí của thuật toán băm mật khẩu tốt

  • Không thể đảo ngược: Không có cách nào lấy lại mật khẩu gốc từ chuỗi băm.
  • Sử dụng “salt”:
    • Salt là một chuỗi ngẫu nhiên được thêm vào mật khẩu trước khi băm.
    • Salt giúp mỗi mật khẩu giống nhau nhưng của 2 người khác nhau sẽ cho ra kết quả băm khác nhau.
    • Chống lại tấn công dùng bảng rainbow table.
  • Có thể điều chỉnh độ phức tạp:
    • Có thể tăng số vòng lặp, lượng RAM, thời gian xử lý.
    • Giúp thuật toán vẫn an toàn dù phần cứng ngày càng mạnh.
  • Memory-hard:
    • Thuật toán tiêu tốn nhiều RAM, làm các cuộc tấn công bằng phần cứng chuyên dụng (GPU, ASIC) trở nên tốn kém, chậm chạp.

3. Các thuật toán băm mật khẩu hiện đại (2025)

Modal Password Hashing

3.1. Argon2

Giới thiệu

  • Argon2 là thuật toán băm mật khẩu hiện đại, chiến thắng cuộc thi Password Hashing Competition (PHC) năm 2015 và được cộng đồng bảo mật quốc tế thừa nhận là chuẩn vàng cho việc bảo vệ mật khẩu.
  • Được thiết kế để khắc phục điểm yếu của các thuật toán cũ, Argon2 cung cấp khả năng chống lại các kiểu tấn công brute-force, tấn công trên GPU/ASIC và các tấn công side-channel.

Các biến thể của Argon2

  • Argon2i: Tối ưu cho xác thực, chống tấn công side-channel (tấn công qua kênh phụ như đo thời gian, điện năng).
  • Argon2d: Tối ưu chống lại tấn công bằng GPU, phù hợp cho các trường hợp bảo vệ dữ liệu mã hóa.
  • Argon2id (được khuyên dùng): Kết hợp ưu điểm của cả hai biến thể trên, cân bằng giữa bảo vệ chống side-channel và brute-force bằng phần cứng chuyên dụng. Đây là lựa chọn nên dùng cho băm mật khẩu người dùng.

Ưu điểm nổi bật

  • Memory-hard: Tiêu tốn nhiều bộ nhớ và CPU, gây khó khăn cho tấn công brute-force bằng phần cứng chuyên dụng như GPU, ASIC.
  • Có thể cấu hình: Cho phép điều chỉnh các thông số như:
    • Dung lượng RAM sử dụng
    • Số vòng lặp (iterations)
    • Số luồng xử lý (parallelism) => Tăng độ an toàn theo nhu cầu thực tế và khả năng phần cứng.
  • Hỗ trợ rộng rãi: Được tích hợp trong nhiều ngôn ngữ và thư viện phổ biến: Python (argon2-cffi), PHP (>=7.2), Rust, .NET, Go, Node.js, libsodium, v.v.
  • Được khuyến nghị bởi các tổ chức bảo mật hàng đầu: OWASP, NIST, các chuyên gia bảo mật quốc tế.

Ví dụ sử dụng Argon2 trong các ngôn ngữ phổ biến

Python (argon2-cffi)
from argon2 import PasswordHasher ph = PasswordHasher(time_cost=2, memory_cost=102400, parallelism=8)
# Băm mật khẩu
hash = ph.hash("matkhau_cua_ban")
print("Hash:", hash)
# Xác thực mật khẩu
try: ph.verify(hash, "matkhau_cua_ban") print("Mật khẩu đúng!")
except: print("Sai mật khẩu!")
  • time_cost: số vòng lặp (iterations)
  • memory_cost: bộ nhớ sử dụng (KB)
  • parallelism: số luồng xử lý
PHP (>=7.2)
<?php
$password = 'matkhau_cua_ban';
$hash = password_hash($password, PASSWORD_ARGON2ID);
echo "Hash: $hash\n"; // Kiểm tra mật khẩu
if (password_verify($password, $hash)) { echo "Mật khẩu đúng!";
} else { echo "Sai mật khẩu!";
}
?>
  • Tham số có thể cấu hình thêm như memory_cost, time_cost, threads trong password_hash.
Node.js (argon2)
const argon2 = require('argon2'); (async () => { const hash = await argon2.hash('matkhau_cua_ban', { type: argon2.argon2id, memoryCost: 2 ** 16, // 64 MB timeCost: 3, parallelism: 4, }); console.log('Hash:', hash); // Kiểm tra mật khẩu if (await argon2.verify(hash, 'matkhau_cua_ban')) { console.log('Mật khẩu đúng!'); } else { console.log('Sai mật khẩu!'); }
})();
Rust (argon2 crate)
use argon2::{self, Config}; fn main() { let password = b"matkhau_cua_ban"; let salt = b"muoi_ngau_nhien"; // Salt luôn phải sinh ngẫu nhiên cho mỗi user let config = Config::default(); let hash = argon2::hash_encoded(password, salt, &config).unwrap(); println!("Hash: {}", hash); // Kiểm tra mật khẩu let matches = argon2::verify_encoded(&hash, password).unwrap(); if matches { println!("Mật khẩu đúng!"); } else { println!("Sai mật khẩu!"); }
}
.NET (C#, dùng thư viện Konscious.Security.Cryptography)
using Konscious.Security.Cryptography;
using System.Text; string password = "matkhau_cua_ban";
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{ Salt = Encoding.UTF8.GetBytes("muoi_ngau_nhien"), DegreeOfParallelism = 8, // số luồng MemorySize = 65536, // 64 MB Iterations = 3
};
byte[] hashBytes = argon2.GetBytes(32);
string hash = Convert.ToBase64String(hashBytes);
Console.WriteLine("Hash: " + hash);
// Quá trình xác thực thực hiện tương tự

Lưu ý: Salt (muối) nên sinh ngẫu nhiên cho mỗi người dùng và lưu kèm với chuỗi hash.


Tổng kết

  • Argon2id là lựa chọn tối ưu và hiện đại nhất cho băm mật khẩu năm 2025.
  • Dễ cấu hình, hỗ trợ đa nền tảng và được khuyến nghị bởi các chuyên gia bảo mật.
  • Hãy luôn sử dụng salt ngẫu nhiên và điều chỉnh thông số phù hợp với khả năng phần cứng của hệ thống!

3.2. yescrypt

Giới thiệu

  • yescrypt là thuật toán băm mật khẩu hiện đại, phát triển dựa trên nền tảng của scrypt nhưng được nâng cấp mạnh mẽ về bảo mật và hiệu năng.
  • yescrypt được thiết kế để giải quyết các vấn đề của các thuật toán cũ khi đối mặt với phần cứng tấn công hiện đại như GPU, ASIC, FPGA.

Ưu điểm

  • Kế thừa và nâng cấp từ scrypt:

    • yescrypt giữ lại đặc tính memory-hard của scrypt (tiêu tốn nhiều RAM) và bổ sung thêm các cơ chế bảo vệ chống lại tấn công brute-force bằng phần cứng mạnh.
    • yescrypt cải thiện khả năng mở rộng, chống lại các kỹ thuật tối ưu hóa tấn công như time-memory trade-off (đánh đổi thời gian - bộ nhớ).
  • Khó bị tấn công bởi phần cứng chuyên dụng:

    • Các thông số của yescrypt khiến việc sử dụng GPU/ASIC để bẻ khóa (cracking) trở nên rất tốn kém và chậm.
    • Bổ sung các lớp bảo vệ sâu hơn như ROM-port hardening để ngăn chặn tấn công sử dụng các thiết bị tùy chỉnh.
  • Được chọn làm mặc định trong glibc (Linux) từ 2023:

    • Kể từ năm 2023, yescrypt đã trở thành thuật toán băm mật khẩu mặc định trong thư viện tiêu chuẩn glibc của Linux (Fedora, Debian, ...).
    • Điều này giúp tăng mức độ bảo mật mặc định cho các hệ thống Unix/Linux khi người dùng khởi tạo, thay đổi hoặc xác thực mật khẩu.

Nhược điểm

  • Chưa phổ biến như Argon2, ít thư viện hơn:
    • So với Argon2, yescrypt chưa được tích hợp rộng rãi vào các framework, ngôn ngữ lập trình phổ biến.
    • Tài liệu, ví dụ và thư viện chính thức còn hạn chế, chủ yếu tập trung ở môi trường Unix/Linux.

Ví dụ sử dụng yescrypt

Linux/Unix (glibc)
  • Khi bạn đổi hoặc đặt mật khẩu mới trên các hệ điều hành Linux hiện đại (Fedora, Debian từ năm 2023), yescrypt sẽ được sử dụng mặc định.
  • Bạn có thể kiểm tra loại thuật toán băm bằng lệnh:
    sudo grep yescrypt /etc/login.defs
    
  • Dòng hash mật khẩu trong /etc/shadow sẽ bắt đầu bằng $y$ nếu dùng yescrypt.
C/C++ (thư viện yescrypt)
  • Có thể tích hợp trực tiếp thư viện yescrypt từ https://github.com/bsdphk/yescrypt.
  • Đoạn mã ví dụ đơn giản:
    #include "yescrypt.h" yescrypt_shared_t shared;
    yescrypt_local_t local; // Khởi tạo
    yescrypt_init_shared(&shared, NULL, 0, YESCRYPT_RW, 0, 0, 0, NULL, 0);
    yescrypt_init_local(&local); // Băm mật khẩu
    const char *password = "matkhau_cua_ban";
    const uint8_t *salt = (const uint8_t *)"muoi_ngau_nhien";
    uint8_t hash[64];
    yescrypt_kdf(&shared, &local, (const uint8_t *)password, strlen(password), salt, strlen((const char *)salt), 16384, 8, 1, 0, hash, sizeof(hash)); // Giải phóng tài nguyên
    yescrypt_free_local(&local);
    yescrypt_free_shared(&shared);
    
  • Lưu ý: Việc sử dụng yescrypt ở mức độ ứng dụng đa phần dành cho các hệ thống nhúng, tool chuyên dụng, hoặc tích hợp bảo mật cấp thấp.
Python
  • Hiện tại, thư viện yescrypt cho Python rất hiếm và chưa chính thức.
  • Tuy nhiên, bạn có thể sử dụng pycrypt hoặc gọi trực tiếp thư viện C bằng ctypes nếu cần.
PHP, Node.js, .NET
  • Chưa có thư viện chính thức phổ biến cho yescrypt. Nếu cần, nên cân nhắc dùng Argon2 hoặc scrypt.

Tổng kết

  • yescrypt là lựa chọn rất mạnh và hiện đại cho hệ thống xác thực mật khẩu, đặc biệt trên nền tảng Linux.
  • Tuy chưa phổ biến như Argon2, nhưng yescrypt đang ngày càng được chấp nhận rộng rãi nhờ mức độ bảo mật cao và được chọn làm mặc định trong hệ sinh thái Linux.
  • Nếu xây dựng ứng dụng đa nền tảng hoặc cần tích hợp với nhiều ngôn ngữ, nên cân nhắc Argon2. Nếu tập trung vào môi trường Unix/Linux và cần bảo mật tối đa, yescrypt là lựa chọn lý tưởng.

3.3. scrypt

Giới thiệu

  • scrypt là một thuật toán băm mật khẩu được thiết kế để tiêu tốn nhiều tài nguyên bộ nhớ (memory-hard), giúp chống lại các cuộc tấn công brute-force sử dụng phần cứng chuyên dụng như GPU hoặc ASIC.
  • Được phát triển bởi Colin Percival vào năm 2009 với mục đích bảo vệ mật khẩu người dùng khỏi các cuộc tấn công quy mô lớn, scrypt từng là chuẩn vàng trước khi Argon2 và yescrypt ra đời.
  • scrypt không chỉ được sử dụng trong bảo mật mật khẩu mà còn là thành phần cốt lõi của một số loại tiền mã hóa như Litecoin.

Ưu điểm

  • Memory-hard, chống tấn công phần cứng:
    • scrypt yêu cầu rất nhiều RAM trong quá trình băm, khiến các công cụ tấn công dựa trên phần cứng (GPU, ASIC) trở nên kém hiệu quả và tốn kém chi phí.
    • Điều này làm cho việc brute-force mật khẩu trở nên rất khó khăn và đắt đỏ về mặt tài nguyên.
  • Được dùng rộng rãi trong các hệ thống cũ và tiền mã hóa:
    • Trước khi Argon2 ra đời, scrypt là lựa chọn mặc định cho nhiều hệ quản trị, hệ thống xác thực và đặc biệt là tiền mã hóa như Litecoin, Dogecoin.

Nhược điểm

  • Kém linh hoạt và hiệu quả hơn Argon2:
    • Khả năng cấu hình (về độ mạnh, khả năng mở rộng, tinh chỉnh thông số) của scrypt không đa dạng bằng Argon2.
    • Argon2 cung cấp bảo mật cao hơn với hiệu năng tối ưu hơn trên phần cứng hiện đại.
  • Thư viện hỗ trợ có thể ít cập nhật hơn so với Argon2.

Ví dụ sử dụng scrypt trong các ngôn ngữ phổ biến

Python (thư viện hashlib từ Python 3.6+)
import hashlib
import os password = b"matkhau_cua_ban"
salt = os.urandom(16)
key = hashlib.scrypt( password, salt=salt, n=16384, # CPU/memory cost factor (2**14) r=8, # block size p=1, # parallelization factor dklen=64 # length of derived key
)
print("Salt:", salt.hex())
print("Hash:", key.hex())
  • Lưu ý: salt cần lưu kèm với hash để xác thực lại sau này.
Node.js (thư viện crypto, từ Node 10+)
const crypto = require('crypto');
const password = 'matkhau_cua_ban';
const salt = crypto.randomBytes(16); crypto.scrypt(password, salt, 64, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => { if (err) throw err; console.log('Salt:', salt.toString('hex')); console.log('Hash:', derivedKey.toString('hex'));
});
PHP (hàm password_hash từ PHP 7.2+)
<?php
$password = 'matkhau_cua_ban';
$hash = password_hash($password, PASSWORD_DEFAULT); // Mặc định là bcrypt, nhưng từ PHP 7.2+ có thể dùng scrypt qua ext-sodium hoặc libsodium
echo "Hash: $hash\n"; // Nếu dùng libsodium:
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES);
$hash = sodium_crypto_pwhash_str( $password, SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
echo "Hash với libsodium: $hash\n";
?>
Go (package golang.org/x/crypto/scrypt)
package main import ( "fmt" "golang.org/x/crypto/scrypt" "crypto/rand"
) func main() { password := []byte("matkhau_cua_ban") salt := make([]byte, 16) rand.Read(salt) hash, err := scrypt.Key(password, salt, 16384, 8, 1, 64) if err != nil { panic(err) } fmt.Printf("Salt: %x\n", salt) fmt.Printf("Hash: %x\n", hash)
}

Tổng kết

  • scrypt vẫn là một lựa chọn an toàn nếu Argon2 hoặc yescrypt không khả dụng, đặc biệt với các hệ thống cũ.
  • Tuy nhiên, nếu có thể, hãy ưu tiên sử dụng Argon2id cho bảo mật mật khẩu người dùng trong các hệ thống mới.
  • Luôn sử dụng salt ngẫu nhiên và lưu thông số cấu hình để xác thực lại mật khẩu về sau.

3.4. bcrypt

Giới thiệu

  • bcrypt là một thuật toán băm mật khẩu được thiết kế vào năm 1999 bởi Niels Provos và David Mazières, dựa trên thuật toán mã hóa Blowfish.
  • bcrypt đã được sử dụng rộng rãi và kiểm chứng qua thời gian, là lựa chọn mặc định trong nhiều hệ thống quản trị, framework và thư viện bảo mật.
  • bcrypt tự động sinh “salt” cho mỗi mật khẩu và cho phép điều chỉnh độ phức tạp thông qua tham số số vòng lặp (cost).

Ưu điểm

  • Được kiểm chứng nhiều năm & phổ biến:
    • Được sử dụng rộng rãi trong thực tế, xuất hiện như mặc định trong nhiều framework (Django, Rails, Laravel, v.v.).
    • Rất khó bị tấn công nếu cấu hình cost đủ lớn.
  • Tích hợp sẵn salt:
    • bcrypt tự động sinh salt ngẫu nhiên cho từng mật khẩu, giúp chống lại tấn công rainbow table.
  • Cấu hình được độ phức tạp (cost):
    • Có thể tăng số vòng lặp (cost) để tăng thời gian băm mật khẩu, phù hợp tình hình phần cứng hiện tại.

Nhược điểm

  • Không memory-hard như Argon2/scrypt/yescrypt:
    • bcrypt chủ yếu tiêu tốn CPU, không yêu cầu nhiều RAM, do đó các cuộc tấn công bằng GPU hoặc ASIC vẫn hiệu quả hơn so với các thuật toán memory-hard như Argon2, scrypt hay yescrypt.
  • Giới hạn độ dài mật khẩu:
    • bcrypt chỉ xử lý tối đa 72 ký tự đầu tiên của mật khẩu, các ký tự tiếp theo sẽ bị bỏ qua.
  • Không tối ưu cho đa luồng hoặc hệ thống phần cứng hiện đại.

Ví dụ sử dụng bcrypt trong các ngôn ngữ phổ biến

Python (thư viện bcrypt)
import bcrypt password = b"matkhau_cua_ban"
# Sinh salt và băm
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
print("Hash:", hashed.decode()) # Kiểm tra mật khẩu
if bcrypt.checkpw(password, hashed): print("Mật khẩu đúng!")
else: print("Sai mật khẩu!")
  • rounds: số vòng lặp (cost), nên đặt từ 12 trở lên.
PHP
<?php
$password = 'matkhau_cua_ban';
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
echo "Hash: $hash\n"; // Kiểm tra mật khẩu
if (password_verify($password, $hash)) { echo "Mật khẩu đúng!";
} else { echo "Sai mật khẩu!";
}
?>
Node.js (thư viện bcryptjs hoặc bcrypt)
const bcrypt = require('bcryptjs'); const password = 'matkhau_cua_ban';
const saltRounds = 12; bcrypt.hash(password, saltRounds, function(err, hash) { if (err) throw err; console.log('Hash:', hash); // Kiểm tra mật khẩu bcrypt.compare(password, hash, function(err, result) { if (result) { console.log('Mật khẩu đúng!'); } else { console.log('Sai mật khẩu!'); } });
});
Go (golang.org/x/crypto/bcrypt)
package main import ( "fmt" "golang.org/x/crypto/bcrypt"
) func main() { password := []byte("matkhau_cua_ban") hash, err := bcrypt.GenerateFromPassword(password, 12) if err != nil { panic(err) } fmt.Printf("Hash: %s\n", hash) // Kiểm tra mật khẩu err = bcrypt.CompareHashAndPassword(hash, password) if err == nil { fmt.Println("Mật khẩu đúng!") } else { fmt.Println("Sai mật khẩu!") }
}

Tổng kết

  • bcrypt vẫn là lựa chọn an toàn và phổ biến cho nhiều hệ thống, đặc biệt khi chưa thể triển khai các thuật toán mới như Argon2, scrypt, yescrypt.
  • Tuy nhiên, nếu có thể, hãy ưu tiên các thuật toán memory-hard hiện đại hơn cho bảo mật tốt nhất.
  • Luôn sử dụng giá trị cost cao (>=12) và chú ý tới giới hạn chiều dài mật khẩu khi dùng bcrypt.

3.5. PBKDF2

Giới thiệu

  • PBKDF2 (Password-Based Key Derivation Function 2) là một thuật toán dẫn xuất khóa dựa trên mật khẩu, được tiêu chuẩn hóa trong RFC 8018 (trước đây là RFC 2898).
  • PBKDF2 được sử dụng rộng rãi trong nhiều hệ thống, thư viện, framework với mục đích chuyển đổi mật khẩu văn bản thành một chuỗi băm an toàn để lưu trữ hoặc dùng làm khóa mã hóa.

Ưu điểm

  • Chuẩn hóa, hỗ trợ rộng:
    • Là một phần của nhiều tiêu chuẩn bảo mật quốc tế (PKCS #5, RFC 8018).
    • Được hỗ trợ rộng rãi ở hầu hết các ngôn ngữ lập trình, hệ điều hành, thư viện mật khẩu và các framework web.
    • Dễ tích hợp vào các hệ thống hiện tại.
  • Có thể cấu hình số vòng lặp (iterations):
    • Có thể tăng số lần lặp để tăng độ mạnh của quá trình băm.

Nhược điểm

  • Không memory-hard:
    • PBKDF2 chỉ tiêu tốn CPU, không yêu cầu nhiều RAM, vì vậy các cuộc tấn công bằng phần cứng (GPU, ASIC) sẽ hiệu quả hơn so với các thuật toán như Argon2, scrypt, yescrypt.
  • Yếu hơn các thuật toán mới:
    • Dù vẫn an toàn nếu cấu hình đúng (nhiều vòng lặp), PBKDF2 không bảo vệ tốt bằng các thuật toán memory-hard hiện đại.
  • Độ mạnh phụ thuộc lớn vào số vòng lặp:
    • Nếu số vòng lặp đặt quá thấp sẽ dễ bị brute-force.

Ví dụ sử dụng PBKDF2 trong các ngôn ngữ phổ biến

Python (thư viện hashlib)
import hashlib
import os password = b"matkhau_cua_ban"
salt = os.urandom(16)
dk = hashlib.pbkdf2_hmac( 'sha256', # Thuật toán hash cơ sở password, salt, 100_000 # Số vòng lặp (iterations)
)
print("Salt:", salt.hex())
print("Hash:", dk.hex())
  • Lưu ý: Nên dùng ít nhất 100.000 vòng lặp cho bảo mật hiện đại.
Node.js (thư viện crypto)
const crypto = require('crypto');
const password = 'matkhau_cua_ban';
const salt = crypto.randomBytes(16); crypto.pbkdf2(password, salt, 100000, 64, 'sha256', (err, derivedKey) => { if (err) throw err; console.log('Salt:', salt.toString('hex')); console.log('Hash:', derivedKey.toString('hex'));
});
PHP
<?php
$password = 'matkhau_cua_ban';
$salt = random_bytes(16);
$hash = hash_pbkdf2('sha256', $password, $salt, 100000, 64, false);
echo "Salt: " . bin2hex($salt) . "\n";
echo "Hash: $hash\n";
?>
Java (javax.crypto)
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.Base64; public class PBKDF2Example { public static void main(String[] args) throws Exception { String password = "matkhau_cua_ban"; SecureRandom sr = new SecureRandom(); byte[] salt = new byte[16]; sr.nextBytes(salt); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100_000, 256); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = skf.generateSecret(spec).getEncoded(); System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt)); System.out.println("Hash: " + Base64.getEncoder().encodeToString(hash)); }
}

Tổng kết

  • PBKDF2 là lựa chọn an toàn và phổ biến khi các thuật toán hiện đại hơn (Argon2, scrypt, yescrypt) không khả dụng.
  • Tuy nhiên, nên ưu tiên Argon2 hoặc các thuật toán memory-hard nếu có thể, để bảo vệ mật khẩu tốt hơn trước các cuộc tấn công phần cứng hiện đại.
  • Luôn dùng salt ngẫu nhiên và số vòng lặp lớn (>=100.000) khi áp dụng PBKDF2.

4. Thuật toán KHÔNG nên dùng cho mật khẩu

Thuật toán Lý do không nên dùng
SHA-1, SHA-256 Quá nhanh, dễ brute-force nếu không có salt/rounds
MD5 Đã bị phá, không an toàn

Giải thích chi tiết

  • SHA-1, SHA-256:

    • Đây là các thuật toán băm mật mã học phổ biến nhưng được thiết kế cho tốc độ nhanh và hiệu suất cao, không phù hợp với yêu cầu bảo vệ mật khẩu.
    • Khi dùng trực tiếp để băm mật khẩu (kể cả kết hợp salt), chúng vẫn chạy quá nhanh, dễ bị brute-force hoặc tấn công bằng phần cứng hiện đại (GPU, FPGA).
    • Nếu không tăng số vòng lặp hoặc thêm các lớp bảo vệ khác, hacker chỉ cần thử hàng triệu mật khẩu mỗi giây.
  • MD5:

    • Đã bị phá vỡ từ lâu, dễ bị tấn công va chạm (collision) và đảo ngược (reverse).
    • Có rất nhiều công cụ, bảng Rainbow Table giúp khôi phục lại mật khẩu gốc từ hash MD5 trong tích tắc.
    • Không được công nhận là an toàn cho bất kỳ mục đích bảo mật nào, đặc biệt là lưu trữ mật khẩu.

Tóm lại:
Không nên dùng các thuật toán băm nhanh như SHA-1, SHA-256, MD5 cho mật khẩu – dù có thêm salt hay không. Hãy sử dụng các thuật toán chuyên biệt bảo vệ mật khẩu như Argon2, yescrypt, scrypt, bcrypt hoặc PBKDF2!

5. Khuyến nghị lựa chọn 2025

Thuật toán Khuyến nghị Ghi chú
Argon2id ✅ Rất nên dùng Tốt nhất hiện nay, hỗ trợ rộng rãi
yescrypt ✅ Mạnh mẽ Linux mặc định, bảo mật cao
scrypt ⚠️ Có thể dùng Cũ hơn, vẫn mạnh, nhưng ít linh hoạt
bcrypt ⚠️ Tạm ổn Nếu không dùng được các thuật toán mới hơn
PBKDF2 ⚠️ Trung bình Yếu hơn, nhưng tốt hơn SHA/MD5
SHA1/MD5 ❌ Không nên dùng Không an toàn

6. Kết luận

  • Không bao giờ lưu mật khẩu dạng plain text.
  • Sử dụng các thuật toán băm mật khẩu hiện đại (Argon2id, yescrypt, scrypt, bcrypt, PBKDF2).
  • Luôn dùng “salt” và tăng độ phức tạp khi cần thiết.
  • Cập nhật thuật toán khi có tiêu chuẩn mới.

Hãy bảo vệ người dùng của bạn ngay từ gốc rễ – bảo vệ mật khẩu bằng thuật toán băm an toàn!


Còn bạn đang dùng thuật toán băm nào để lưu trữ mật khẩu?
Hãy kiểm tra lại hệ thống của mình và cân nhắc nâng cấp nếu cần thiết!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Nguyên lý hoạt động về ứng dụng hàm băm

Trong bài viết này, mình sẽ trình bày về nguyên lý hoạt động về ứng dụng hàm băm cho việc xác thực thông tin và cho chữ ký số, giải thích, cũng như so sánh ưu, khuyết điểm của mỗi loại. Chú

0 0 497

- vừa được xem lúc

SHA1, SHA2, SHA256 – Những thuật ngữ này nghĩa là gì?

Nếu như bạn đã từng nghe về các từ vựng “SHA” mà vẫn chưa chắc chắn đã hiểu chúng, bài viết này sẽ làm rõ những thuật ngữ này. Nhưng trước khi đề cập đến SHA, chúng ta cần tìm hiểu mã hàm băm là gì, s

0 0 17

- vừa được xem lúc

[Tối ưu Performance ứng dụng nodejs] bcryptjs hay bcrypt

[Tối ưu Performance ứng dụng nodejs] bcryptjs hay bcrypt. Cho bạn nào chưa biết, Bcrypt là một dạng hash function thường sử dụng để mã hóa password với mục đích tăng cường bảo mật.

0 0 74

- vừa được xem lúc

Blog#25: Tất cả những gì bạn cần để xây dựng một Node.js Server & Authentication (Cơ bản): Express, Sessions, Passport, and cURL - Part 2/2 😊 (Series: Bí kíp Javascript - PHẦN 21)

Mình là TUẤN hiện đang là một Full-stack Developer tại Tokyo . Nếu bạn thấy Blog này hay xin hãy cho mình một like và POST ký để ủng hộ mình nhé .

0 0 27

- vừa được xem lúc

Blog#24: Tất cả những gì bạn cần để xây dựng một Node.js Server & Authentication (Cơ bản): Express, Sessions, Passport, and cURL - Part 1/2 😊 (Series: Bí kíp Javascript - PHẦN 20)

Mình là TUẤN hiện đang là một Full-stack Developer tại Tokyo . Ngày xưa lúc mình mới tiếp cận với Nodejs và đọc các tutorial hướng dẫn trêng mạng, mình luôn phải vật lộn với việc hiểu phần Authenticat

0 0 28

- vừa được xem lúc

Hướng dẫn finetune mô hình LLM đơn giản và miễn phí với Unsloth

Chào mừng các bạn đến với bài viết hướng dẫn chi tiết cách finetune (tinh chỉnh) một mô hình ngôn ngữ lớn (LLM) một cách đơn giản và hoàn toàn miễn phí sử dụng thư viện Unsloth. Trong bài viết này, ch

0 0 8