Không thể "giải mã" hash cũ, nhưng có hai chiến lược nâng cấp mà user không phải làm gì.
Cách 1 — nâng cấp khi đăng nhập (lazy rehash). Giữ nguyên hash cũ, thêm cột đánh dấu thuật toán. Khi user đăng nhập thành công, ta đang cầm mật khẩu dạng rõ trong bộ nhớ → hash lại bằng thuật toán mới rồi ghi đè.
const user = await findByEmail(email)
const ok = user.algo === 'md5'
? md5(password) === user.hash
: await argon2.verify(user.hash, password)
if (!ok) return fail()
if (user.algo !== 'argon2id' || argon2.needsRehash(user.hash, PARAMS)) {
await updateHash(user.id, await argon2.hash(password), 'argon2id')
}Nhược điểm: user không bao giờ đăng nhập thì hash yếu vẫn nằm đó.
Cách 2 — bọc hash cũ ngay lập tức (layered/wrapped hashing). Chạy một migration hàng loạt, đưa hash cũ qua thuật toán mới: argon2id(md5(password)), lưu cờ wrapped=true. Toàn bộ DB an toàn ngay trong một lần chạy, không cần biết mật khẩu. Khi verify thì tính md5 trước rồi verify Argon2. Sau đó vẫn nên kết hợp cách 1 để dần chuyển về dạng thuần argon2id(password).
Vận hành đi kèm: lưu định danh thuật toán + tham số ngay trong chuỗi hash (bcrypt/Argon2 đã làm sẵn) để lần đổi tham số sau chỉ là lazy rehash; bắt buộc đổi mật khẩu chỉ với nhóm tài khoản có dấu hiệu bị lộ; và cùng lúc kiểm tra mật khẩu đang dùng có nằm trong tập đã lộ hay không.