Hướng dẫn mã hóa password nodejs

Mình không thực sự hiểu nhiều về các thuật toán, kĩ thuật mã hóa mật khẩu. Mình chỉ đọc qua một số phương pháp mã hóa và các lời bình về nó trên mạng và quyết định sử dụng bcrypt. Đọc thì thấy rằng thuật toán này tuy có hơi chậm hơn các thuật toán khác như MD5, nhưng đổi lại nó giải quyết được các vấn đề như hack từ điển… của các thuật toán khác do có thể đối phó được với cấp độ tiến hóa của vi xử lý máy tính.

Bạn nào quan tâm thì có thể tìm hiểu thêm ở bài viết How To Safely Store A Password và bài báo khoa học A Future-Adaptable Password Scheme - 1999 USENIX Annual Technical Conference này nhé. Cũng thông qua bài này, mình mong rằng có bạn nào hiểu về kĩ thuật mã hóa có thể chỉ giáo cho mình rõ hơn vì bản thân có tìm hiểu nhưng chẳng hiểu gì :((

Giờ đi vào code thôi ^^.

bcrypt không được hỗ trợ trực tiếp trong bộ thư viện mã hóa crypt của Node.js nên ta phải sửa dụng gói nodejs bên ngoài. Qua tìm hiểu mình thấy rằng gói bcrypt này được tìm nhiều người sử dụng và đánh giá cao nên cũng quyết định sử dụng nó.

Đầu tiên là cài gói này ứng dụng đã:

npm install bcrypt --save

Gói này hỗ trợ cả 2 phương thức là bất đồng bộ và đồng bộ nhưng vì thời gian mã hóa có thể lâu nên phương thức bất đồng bộ được gợi ý sử dụng hơn cả. Trong bài viết này sẽ đề cập tới cả 2 phương thức đó.

Bất đồng bộ

const bcrypt = require('bcrypt')

bcrypt.genSalt(10, function (err, salt) {
    bcrypt.hash('B4c0/\/', salt, function (err, hash) {

        console.log(hash)

        // To check a password  
        bcrypt.compare('B4c0/\/', hash, function (err, res) {
            // res == true
            console.log('equal')
            console.log(res)
        })

        bcrypt.compare('not_bacon', hash, function (err, res) {
            // res == false
            console.log('not equal')
            console.log(res)
        })
    })
})

// Auto-gen a salt and hash
bcrypt.hash('bacon', 8, function (err, hash) {
    console.log(`Auto-gen: ${hash}`)
})

Đồng bộ

const bcrypt = require('bcrypt')

var salt = bcrypt.genSaltSync(10)
var hash = bcrypt.hashSync('B4c0/\/', salt)

// To check a password
var res = bcrypt.compareSync('B4c0/\/', hash)   // true
console.log('equal')
console.log(res)

res = bcrypt.compareSync('not_bacon', hash)     // false
console.log('not equal')
console.log(res)

// Auto-gen a salt and hash
var hash = bcrypt.hashSync('bacon', 8)
console.log(`Auto-gen: ${hash}`)

2 đoạn mã trên sau khi thực thi sẽ cho kết quả như sau:

$2a$08$vj/8fY70VkEmVab/czGJ8euRvsGo0q0T5ETDKUusCXMXytYXeCKkC

equal
true

not equal
false

Auto-gen: $2a$10$LLsJqwOAirlXQW5DVt1pDeyPTWM5qoooqHjkAjA68iIp2mpcCfdZ2

Với bcrypt ta cần chú ý tới rounds (work factor) truyền vào để sinh salt. Với rounds càng lớn thì càng bảo mật nhưng thời gian xử lý cũng mất nhiều hơn. Đơn giản chỉ có vậy thôi.

Để tìm hiểu thêm về các API của gói này bạn có thể tham khảo tại đây.

Mã hóa mật khẩu người dùng trước khi lưu vào database là 1 chuyện bắt buộc phải làm đối với bất cứ 1 website nào. Không riêng gì ai, Nodejs cũng hỗ trợ khá nhiều thư viện để hỗ trợ việc mã hóa này.

Hướng dẫn mã hóa password nodejs
Ảnh: http://websitedesign.schoolsict.co.uk

bcrypt.js

Bcrypt được sử dụng khá nhiều, ban đầu được thiết kế bởi Niels Provos và David Mazières, xem thêm thông tin tại wikipedia.

Cài đặt

Cách sử dụng

async (recommended):

Để mã hóa mật khẩu

var bcrypt = require('bcrypt');
bcrypt.genSalt(10, function(err, salt) {
    bcrypt.hash('B4c0/\/', salt, function(err, hash) {
        // Store hash in your password DB.
    });
});

Để kiểm tra mật khẩu:

// Load hash from your password DB.
bcrypt.compare('B4c0/\/', hash, function(err, res) {
    // res == true
});
bcrypt.compare('not_bacon', hash, function(err, res) {
    // res == false
});

Auto-gen a salt and hash:

bcrypt.hash('bacon', 8, function(err, hash) {
});

sync

Để mã hóa mật khẩu:

var bcrypt = require('bcrypt');
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync('B4c0/\/', salt);
// Store hash in your password DB.

Để kiểm tra mật khẩu:

// Load hash from your password DB.
bcrypt.compareSync('B4c0/\/', hash); // true
bcrypt.compareSync('not_bacon', hash); // false

Auto-gen a salt and hash:

var hash = bcrypt.hashSync('bacon', 8);

Trong các bài toán đơn giản về tạo data cho chức năng đăng ký hoặc đăng nhập bằng Nodejs, password thường ở dạng String và có thể nhìn thấy trong database. Hôm nay, AllXOne sẽ giới thiệu cho các bạn một cách đơn giản để mã hóa password hay còn gọi là “Băm” password bằng cách sử dụng Bcrypt – một thuật toán mã hóa mật khẩu được thiết kế bởi Niels Provos and David Mazières.

Băm là một tấm vé một chiều để mã hóa dữ liệu. Hashing thực hiện chuyển đổi một chiều trên mật khẩu, biến mật khẩu thành một chuỗi khác, được gọi là mật khẩu băm.

Một số điều kiện để chúng ta có thể bắt đầu

Đây là bài viết dành cho các bạn đã có những khái niệm cơ bản về JavaScript, Nodejs và MongoDB:

Đảm bảo rằng bạn đã cài đặt phần mềm sau trên hệ thống của mình:

  • NodeJS
  • MongoDB

Không dài dòng thêm nữa, chúng ta cùng bắt đầu

Bước 1 : Tạo một dự án Nodejs mới và tiến hành cài đặt init

  npm init -y

Bước 2 : Cài đặt Express.js và Mongoose

 npm install --save mongoose express

Bước 3 : Setup Express.js  và kết nối nó với MongoDB bằng mongoose.

  • Tạo một tệp mới có tên là index.js tại thư mục gốc của dự án và thêm đoạn code dưới đây vào:
const { request, response } = require("express");
const express = require("express");
const mongoose = require("mongoose");
const app = express();
const port = 8000;
const userRouter = require("./app/routers/userRouter")
const path = require("path");

app.use(express.json());
app.use(express.urlencoded({
    extended: true
}));


mongoose.connect("mongodb://localhost:27017/Test", (error) => {
    if (error) throw (error)
    console.log("Successfulity connect to MongoDB")
})

app.listen(port, () => {
    console.log("App lisstening on port " + port)
})

Như vậy, chúng ta đã tạo một dự án mới trong MongoDB tên là Test và một máy chủ trên cổng 8000.

Bước 4 Tiếp theo, tạo một thư mục app, trong đó chứa các components con là controller, model, router

Hướng dẫn mã hóa password nodejs

Tại thư mục Model, tạo file userModel.js và thêm đoạn code sau:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;


const userSchema = new Schema({
   
    username: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        unique: true,
        match: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
    },
    password: {
        type: String,
        required: true,
    },
    createdAt: {
        type: Date,
        default: Date.now()
    },
    updatedAt: {
        type: Date,
        default: Date.now()
    }
})



module.exports = mongoose.model("User", userSchema)

Cài đặt bcrypt

npm i --save bcrypt

Tại thư mục Controller, tạo file userController.js và thêm đoạn code sau:

const bcrypt = require("bcrypt");
const userModel= require("../models/userModel");
const mongoose = require("mongoose");
const { request } = require("express");
const { response } = require("express");
const validatePasword = (password) => {
    return password.match(
        // Tối thiểu tám ký tự, ít nhất một chữ cái, một số và một ký tự đặc biệt
        /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/
    );
  };

const createUser = async (request, response) => {
    let bodyRequest = request.body;

    if(!bodyRequest.username) {
        return response.status(400).json({
            status: "Error 400: Bad Request",
            message: "username is required"
        })
    }
    if(!bodyRequest.email) {
        return response.status(400).json({
            status: "Error 400: Bad Request",
            message: "email is required"
        })
    }
 
    if(!bodyRequest.password) {
        return response.status(400).json({
            status: "Error 400: Bad Request",
            message: "password is required"
        })
    }
    if (!validatePasword(bodyRequest.password)) {
        return response.status(400).json({
            status: "Error 400: Bad Request",
            message: "password is not valid"
        })
    }

    let user = new userModel(bodyRequest);
    const salt = await bcrypt.genSalt(10);
    user.password = await bcrypt.hash(user.password, salt);
    user.save((error, doc) => {
        if(error) {
            response.status(500).json({
                status: "Error 500: Email đã được đăng ký",
                message: error.message
            })
        } else {
            response.status(201).send({
                status: "Success 201: Đăng ký thành công",
                doc: doc
            })
        }
    })
}

const logInUser = async (request, response) => {
    let bodyRequest = request.body;
    const user = await userModel.findOne({ email: bodyRequest.email });
    if (user) {
      const validPassword = await bcrypt.compare(bodyRequest.password, user.password);
      if (validPassword) {
        response.status(200).json({ status: "ok" });
      } else {
        response.status(400).json({ error: "Password không đúng!!!" });
      }
    } else {
        response.status(401).json({ error: "Email không đúng!!!" });
    }
}


module.exports = {
    createUser,
    logInUser
}

Tại thư mục router, tạo file userRouter.js và thêm đoạn code sau:

const express = require("express")
const { createUser, logInUser } = require("../controller/userController")
const router = express.Router();

router.post("/register", createUser);
router.post("/login", logInUser);


// exports router
module.exports = router;

Bước 5 : Chạy thử bằng Postman

Chạy dự án bằng lệnh node index.js.

Tạo người dùng mới với một email và mật khẩu bằng cách gửi yêu cầu Post tại http://localhost:8000/register. Lưu ý rằng bạn sẽ nhận được mật khẩu băm trong MogooDB.

Thự hiện đăng nhập bằng cách gửi yêu cầu Post tại http://localhost:8000/login.

Kết quả trong MogooDB sẽ như thế này:

Hướng dẫn mã hóa password nodejs

Chúc các bạn thành công.

Cựu học viên Ironhack Việt Nam – Hiện là full-stack Developer tại All Xone