[npm] Bcrypt를 사용하여 비밀번호 암호화하기

Shin Hyun Woo·2022년 12월 2일
0
post-thumbnail

DB에 사용자가 입력한 정보를 아래와 같이 POST했었다.

{
    "name": "email1234",
    "email": "email1234@naver.com",
    "password": "1234567"
}

근데 저장된 DB를 보면 아래처럼 저장되어 있다.

누가봐도 보안에 매우 취약해보인다.
특히 password는 관리자조차도 알 수 없게 해야하는데 사용자가 입력한 그대로 입력되어있다.

그래서 password를 관리자도 password가 무엇인지 읽을 수 없게 변환해야한다.
이때 유용한 것이 'Bcrypt'이다.

Bcrypt 설치

우선 아래 명령어를 실행하여 'Bcrypt'를 설치한다.

npm i bcrypt --save

Bcrypt 사용

MongoDB의 Schema를 설정한 User.js로 가서 bcrypt를 require한다.

const bcrypt = require('bcrypt');

우선 npm 홈페이지의 Bcrypt 사용법은 아래처럼 작성되어 있다.

나는 salt와 hash를 자동으로 생성하지 않기 때문에 salt를 우선 생성하고 생성된 salt로 password를 암호화 하는 Technique 1로 진행했다.

bcrypt.genSalt(saltRounds, function(err, salt) {
	bcrypt.hash(myPlaintextPassword, salt, function(err, hash){
    	// Store hash in your password DB.
    });
})

여기서 genSalt()는 bcrypt가 password를 암호화하기 위해 필요한 'salt'라는 것을 생성할 때 사용하는 메서드이다.
인자로는 saltRounderr의 정보와 salt를 담은 콜백함수이다.

genSalt()의 첫번째 인자인saltRounds는 기존에 salting된 password를 몇 번 더 salting을 해서 해시를 도출할 것인가를 결정하는 인자라고 생각하면 된다.

여기서 사용되는 salt란 명칭은 음식에 소금을 치면 사방으로 흩어지듯이 설정한 password에 난수를 무작위로 추가하는 역할을 한다.
salt라는 것이 설정한 password를 복잡하게 변경시키는 것이다.

그래서 saltRound를 설정하고 다음으로 넘어간다.

// saltRounds를 10번 실행한다.
const saltRounds = 10; 

그리고 salt를 생성하기 전에 작성했던 register 라우터를 보면 순서를 잘 생각해야한다.

app.post("/register", (req, res) => {
  const user = new User(req.body);

  // 암호화 실행 후...
  
  // DB에 데이터 저장
  user.save((err, userInfo) => {
    if (err) return res.json({ success: false, err });
    return res.status(200).json({
      success: true,
    });
  });
});

여기서 user.save()는 사용자가 입력한 데이터가 DB에 저장되는 것을 말하는데 이 메서드가 실행되기 전에 암호화가 진행되어야하는게 순서상 가장 맞다.
그러므로 salt를 생성하는 genSalt 부분을 pre() 메서드를 사용해서 감싸서 진행한다.
pre()는 mongoose에서 온 메서드이다.

userSchema.pre("save", function (next) {
    const user = this;
    
	bcrypt.genSalt(saltRounds, function(err, salt) {
		bcrypt.hash(myPlaintextPassword, salt, function(err, hash){
    		// Store hash in your password DB.
    	});
	})
  next();
});

pre() 메서드를 사용해서 User모델(schema)에 사용자 정보를 save하기 전에 두번째 인자인 콜백함수를 실행한다.

콜백함수의 파라미터인 nextpre() 함수가 끝이나면 실행하는 함수로 index.jsuser.save로 결과를 전송한다.

이제 pre() 메서드로 감싼 후 내부 함수를 작성한다.

bcrypt.genSalt(saltRounds, function(err, salt) {
  	// salt 생성 시 에러가 발생할 경우 user.save에 err 정보를 전송하고 아니면 다음 단계로 넘어간다.
    if(err) return next(err);
  	// 
	bcrypt.hash(user.password, salt, function(err, hash){
    	if (err) return next(err);
        user.password = hash;
        next();
    });
})

hash() 메서드는 입력받은 password를 salting하여 암호화된 hash로 생성한다.
hash를 생성하는 도중 에러가 발생하면 에러를 user.save으로 전송하고 아니라면 현재 model에 저장된 password에 hash를 오버라이드해주고 다음 단계인 user.save 부분으로 DB에 저장한다.

결과

그럼 아래의 데이터를 입력했을 때 데이터의 결과는 어떤지 보자.

{
    "name": "test1",
    "email": "test1@naver.com",
    "password": "1234567"
}

"1234567"으로 설정했던 password가 암호화되어 복잡한 문자열로 변한 것을 볼 수 있다.

느낀점

요즘 공부를 하면서 가장 느끼는 점이 '공식 문서'의 중요성이다. 강의를 그냥 들으면서 공부하거나 혼자 공부하면서 모르는 부분을 공식 문서를 이해하지 못하더라도 보기라도 하면 에러가 발생했을 때 어느 정도의 예측이 가능한 것 같은 느낌이 든다.
앞으로 공식문서를 참고하면서 공부를 해나가야겠다.

출처 :
bbanderson님 티스토리 블로그
Bcrypt API
Mongoose Docs

profile
untiring_dev - Notion 페이지는 정리 중!

0개의 댓글