Menu
CoddyTech

Không bao giờ lưu mật khẩu

Thuộc phần Xác thực và bảo mật trong hành trình Node.js của Coddy. Bài học 4 trên 24.

Mọi người thường dùng lại mật khẩu, nên mật khẩu dạng văn bản thuần bị lộ có thể khiến các tài khoản khác cũng gặp rủi ro. Hãy lưu mã băm mật khẩu có muối: một giá trị được tạo ra mà bạn xác minh bằng cách tính lại, chứ không phải giải mã.

const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(inviteCode, salt, 32);
const saved = salt + '.' + key.toString('hex');

Ví dụ này kết hợp muối và khóa được tạo ra cho mã mời. Dấu phân cách giúp quá trình xác minh lấy lại muối và tạo khóa khác với cùng các tham số. Bài tập về mật khẩu của bạn dùng dấu phân cách và độ dài khóa riêng. Tạo muối cho mỗi mật khẩu mới, tạo khóa, rồi lưu cả hai. Mã băm có muối vẫn cần mật khẩu mạnh vì kẻ tấn công có thể thử đoán.

challenge icon

Thử thách

Dễ

Hiện tại, khi đăng ký, ứng dụng lưu mật khẩu đúng như cách người dùng nhập. Hãy thay bằng giá trị băm có muối, để nếu cơ sở dữ liệu bị lộ thì mật khẩu vẫn được bảo vệ.

  1. Trong passwords.js, hãy sửa hashPassword(password) để hàm trả về văn bản mà quá trình đăng ký lưu, theo dạng muối, dấu hai chấm, giá trị băm:
    • Muối gồm 16 byte ngẫu nhiên, được tạo mới mỗi lần gọi và ghi dưới dạng hệ thập lục phân: 32 ký tự từ 0 đến 9 và từ a đến f.
    • Giá trị băm là khóa 64 byte mà scrypt tạo từ mật khẩu và muối đó, cũng được ghi dưới dạng hệ thập lục phân: 128 ký tự. Đối số thứ ba của scryptAsync là độ dài khóa tính bằng byte.
    • Nối hai phần bằng dấu hai chấm. Giá trị được lưu trông như sau: f2bba0669d9fa2d203afc7e61bf8f065:c3f7cae352e023a8d4766da9a316b5a2c844100efff22c4079f89a8ab8512ac91c33227481c3afa88c3ba0e88347f9e3e0c22dad9d55f19688a350f71be8dbd2
  2. Bạn không cần sửa server.js. Lệnh POST /signup đã gọi hashPassword và lưu giá trị hàm trả về. Tệp này cũng có GET /debug/users/:username, cho biết dữ liệu đã lưu của một người dùng. Tuyến này chỉ để bạn xem thử; ứng dụng thực tế không bao giờ có tuyến này.

Thử nhé: Nhấn Run. Gửi POST /signup với {"username": "ada", "password": "correct horse"}. Bạn nhận được trạng thái 201 và {"username": "ada"}. Gửi cùng nội dung đó với "username": "bob". Tiếp theo, gửi GET /debug/users/ada, rồi GET /debug/users/bob. Mỗi giá trị passwordHash gồm 32 ký tự, một dấu hai chấm và thêm 128 ký tự. Hai giá trị này khác nhau, dù ada và bob chọn cùng một mật khẩu.

Tự làm thử

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// Chuyển mật khẩu thành chuỗi mà signup lưu trữ.
// Hiện tại, hàm giữ nguyên mật khẩu như đã nhập.
export async function hashPassword(password) {
    return password;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Xác thực và bảo mật

Tự luyện tập: Playground Express