[bcrypt] 사용자 정보 암호화하기

Seok·2020년 12월 28일
1

OAuth2.0 개발기록

목록 보기
3/5
post-thumbnail

현재는 데이터베이스에 사용자 정보를 저장시 비밀번호가 그대로 노출되어있는 형태로 저장된다.

bcrypt를 사용하여 사용자의 비밀번호를 암호화하고 저장하려 한다.


0. Bcrypt

이전까지 node.js로 개발을 할때는 cryto를 사용했지만 이번엔 다른 방식을 찾아보다가 알게되었다.

평문은 노출되니 사용하면 안되고, 해시를 통해 저장하면 평문이 노출되진 않지만 rainbowtable공격이 가능하다.

bcypt는 해시에 공격자가 암호를 유추할 수 없도록, 평문 데이터에 의미 없는 데이터인 salt라는 것을 추가하여 rainbowtable을 어렵게하고, 또한 서버의 컴퓨팅성능에 따라 해싱을 변수(코드상에서 saltRound)로 조절하여 bruteforce공격을 어렵게 한다.


비밀번호 저장

1. 설치

$ npm install bcrypt


2. 적용

아직은 암호화를 사용할 부분이 개발된 API는 회원가입 뿐이다. 그래서 회원가입 서비스 로직에 사용자가 사용하려는 비밀번호에 적용하려한다.

기존에 개발해 둔 서비스 로직에 아래 과정을 추가하면 된다.
1. salt 생성
2. plain text 와 salt를 이용해 hashing
3. 결과와 salt를 저장.

salt값의 저장은 추후 개발될 로그인처리나 인증과정해서 사용하려한다.

bcrypt로 입력한 패스워드와 hash된 패스워드를 검증할 때 salt는 필요없다. salt는 rainbowtable 공격을 막는 역할일 뿐이다.

📄secure.js

import bcrypt from 'bcrypt';
const saltRounds = 12;

export class Secure {
  static genSalt = async () => {
    return bcrypt.genSalt(saltRounds);
  };

  static hash = async (plainPassword, salt) => {
    return bcrypt.hash(plainPassword, salt);
  };
}

추후에 API들이 개발되면서 복호화 과정에서도 쓰일 것 같아 따로 클래스로 선언해서 분리해 두었다.
bcrypt를 import 하고 salt를 생성하는 역할의 genSalt와 사용자가 입력한 패스워트와 salt를 이용하여 해싱을 하는 hash를 선언했다.

saltRounds는 클수록 좋지만 서버 사양이 따라줘야한다. 검색 결과 10~12값을 최소값으로 권장하고 있어서 일단 12로 값을 정해두었다.

📄user.service.js

import { UserDao } from './../dao/user.dao';
import { handleError } from './../model/Error';
import { Secure } from './../util/secure';
export class UserService {
  static createUser = async (req) => {
    let { id, password, name } = req;

    const salt = await Secure.genSalt();
    password = await Secure.hash(password, salt);

    const exsited = await UserDao.getUser(id);

    if (exsited != null) {
      throw new handleError(409, 'User already existed');
    }

    try {
      await UserDao.createUser(id, password, salt, name);
      const result = await UserDao.getUser(id);
      return result;
    } catch (err) {
      throw new handleError(500, 'Create user fail');
    }
  };
}

위에서 생성한 클래스를 import 해주고 회원가입 서비스 로직에서 salt를 생성하고 받아온 평문 비밀번호와 salt를 이용해 해시를 진행 후 그 값을 저장해 주면 된다.


3. 결과 확인

코드 작성 후 logger를 통해 생성된 salt값과 hash값을 확인해 보았다.

DB에 저장도 잘 된다!


비밀번호 검증

1.사용법

bcrypt npm 페이지에서의 사용법은 아래와 같다.

// Load hash from your password DB.
bcrypt.compareSync(myPlaintextPassword, hash); // true
bcrypt.compareSync(someOtherPlaintextPassword, hash); // false

동기/비동기 방식에 따라 함수명이 다르다.


2. 적용

📄secure.js

import bcrypt from 'bcrypt';
const saltRounds = 12;

export class Secure {
  static genSalt = async () => {
    return bcrypt.genSalt(saltRounds);
  };

  static hash = async (plainPassword, salt) => {
    return bcrypt.hash(plainPassword, salt);
  };

  static check = async (plainPassword, hashPassword) => {
    return bcrypt.compareSync(plainPassword, hashPassword);
  };
}

secure.jscheck를 만들어 bcrypt.compareSync의 결과를 반환하도록 만들고,

📄user.service.js

import { UserDao } from './../dao/user.dao';
import { handleError } from './../model/Error';
import { Secure } from './../util/secure';
export class UserService {
  ...(생략)
  static authenticate = async (body) => {
    const { id, password } = body;

    const user = await UserDao.getUserforAuth(id);

    const compareResult = await Secure.check(password, user.password);

    if (compareResult === false) throw new handleError(401, 'Auth Error');

    return compareResult;
  };
}

사용자 서비스 로직에서 해당 함수를 호출하고 결과를 반환하도록 했다. 여기는 추후에 JWT+refresh token을 발급하는 과정이 추가될 예정이라 이렇게만 작성했다.


3. 결과 확인

비밀번호가 맞으면 200, 틀리면 401 에러를 반환하는 것을 확인할 수 있었다.


참고

SHA-1
bcrypt
node.bcrypt.js
검증시 salt가 쓰이지 않는 이유

profile
🦉🦉🦉🦉🦉

0개의 댓글