본문 바로가기
JavaScript/Node

[Node] 암호화(feat. bcrypt)

by kwh_coding 2024. 7. 25.

Bcrypt는 단방향 암호화 알고리즘으로써, 비밀번호 해싱을 위해 설계되었다. 내부적으로 랜덤 salt가 생기고

이에 따른, 해시값의 결과가 매번 바뀌게 된다.


Bcrypt는 랜덤하게 솔트를 생성한 후 비밀번호와 솔트를 결합하여 Blowfish암호를 여러 번 반복하여 해싱한다.

반복 횟수는 cost factor로 결정하며 이를 통해 해싱 속도를 조절한다.

 

그렇기 때문에, Brycpt는 단방향 암호화라고 한다. 입력 데이터를 고정된 크기의 해시 값으로 변환하고

변환된 데이터를 원래의 입력한 데이터로 복원하는 것은 불가능하기 때문이다.

 

/*

salt는 해싱 과정에서 비밀번호에 추가되는 임의의 데이터 조각이라고 생각할 수 있다.

salt가 추가됨으로써 동일한 비밀번호도 서로 다른 해시 값을 가지게 되어 같은 해시 값을 가지는 것을 방지한다.

또한, 레인보우 테이블을 사용한 비밀번호 추측을 어렵게 만든다는 장점도 존재한다.

*/

 

/*

레인보우 테이블(rainbow table)은 해시 함수를 사용하여 변환 가능한 모든 해시 값을 저장시킨 테이블이다.

이를 이용하여 원래 비밀번호를 추출해 내는데 사용되며, 조합 가능한 모든 문자열을 대입하는 방식으로 문제를 풀어내는 

브루트 포스 공격을 뒷받침하는 역할을 한다.

*/

 


 

사용 예제

npm install bcrypt

 

당연히 패키지 설치부터 이루어져야 한다. 

 

실제 내가 사용한 코드를 살펴보면

router.post('/addEmployee', async (req, res) => {
    const { first_name, last_name, department, email, phone, job_title, hire_date, employee_pw, salary } = req.body;

    console.log(req.body);
    try {
        // 비밀번호 해시화
        const hashedPassword = await bcrypt.hash(employee_pw, 10);
        const formattedHireDate = new Date(hire_date).toISOString().split('T')[0]; // YYYY-MM-DD 형식으로 변환

        // 데이터베이스에 추가할 데이터
        const employee = {
            first_name: first_name,
            last_name: last_name,
            department: department,
            email: email,
            phone: phone,
            hire_date: formattedHireDate,
            job_title: job_title,
            employee_pw: hashedPassword, // 해시화된 비밀번호
            salary: salary,
        };

        const sql = 'INSERT INTO employees SET ?';
        connection.query(sql, employee, (err, result) => {
            if (err) {
                console.error('에러:', err);
                res.status(500).send('에러');
            } else {
                res.status(200).send('insert 성공');
            }
        });
    } catch (err) {
        console.error('에러:', err);
    }
});

 

프론트단으로부터 받아온 입력 데이터 중 비밀번호를

const hashedPassword = await bcrypt.hash(employee_pw, 10);

를 통해 해시화 시킨다. 

 

이때, 두 번째 인자인 10은 위에서 말한 cost factor를 의미한다. 

 

 

 

이제 이렇게 INSERT시킨 해시화 데이터를 검증하는 코드이다.

router.post('/loginCheck', async (req, res) => {
    const { email, employee_pw } = req.body;

    const query = 'SELECT employee_id, email, first_name, last_name, employee_pw FROM employees WHERE email = ?';
    connection.query(query, [email], async (error, results) => {
        if (error) {
            console.error('에러:', error);
            return res.status(500).json({ error: '에러' });
        }

        if (results.length > 0) {
            const user = results[0];

            const match = await bcrypt.compare(employee_pw, user.employee_pw);
            if (match) {
                req.session.user = {
                    id: user.employee_id,
                    email: user.email,
                    firstName: user.first_name,
                    lastName: user.last_name,
                };
                res.json({ success: true, user: req.session.user });
            } else {
                res.status(401).json({ error: '에러' });
            }
        } else {
            res.status(401).json({ error: '존재하지 않는 사용자' });
        }
    });
});

 

로그인을 시키는 코드이기 때문에 email과 employee_pw를 기준으로 로그인을 한다. 이때, 데이터베이스에는 해시화된 비밀번호가 들어가 있기 때문에 검증하는 과정이 필요하다.

if (results.length > 0) {
	const user = results[0];

쿼리 결과로 반환된 결과값이 존재하는지 확인한다. 

결과가 나온다면 user 변수에 저장을 하게 된다.

 

            const match = await bcrypt.compare(employee_pw, user.employee_pw);
            if (match) {
                req.session.user = {
                    id: user.employee_id,
                    email: user.email,
                    firstName: user.first_name,
                    lastName: user.last_name,
                };
                res.json({ success: true, user: req.session.user });
            } else {
                res.status(401).json({ error: '에러' });
            }

 

match 변수에는 입력된 비밀번호와 해시된 비밀번호를 비교하여 저장하게 된다.

따라서, true가 저장이 되었다면 사용자 정보를 세션에 저장하고 false라면 오류메세지를 반환하고 프론트단에서 에러를 처리하였다.

 


 

원리는 어렵지만 사용하는 방법은 아주 간단하여 실제 실무에서도 자주 쓰이기 때문에 꼭 연습해 봐야겠다는 생각을 하고 직접 적용을 시켜보았다!

 

'JavaScript > Node' 카테고리의 다른 글

[Node] Socket.io 활용 WebRTC  (0) 2024.08.24
[Node] session  (3) 2024.07.24
[Node] Node.js?  (2) 2024.07.17