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

Shin Hyun Woo·2022년 12월 2일
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'라는 것을 생성할 때 사용하는 메서드이다.
인자로는 saltRound와 err의 정보와 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하기 전에 두번째 인자인 콜백함수를 실행한다.

콜백함수의 파라미터인 next는 pre() 함수가 끝이나면 실행하는 함수로 index.js의 user.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
일어나... 취업해야지...

0개의 댓글