본문 바로가기
Developing Note/node.js

서버에 패스워드 안전하게 저장하는 방법 (Node.js + MongoDB) - bcrypt

by dev_mac-_- 2019. 1. 9.

서버에 패스워드 안전하게 저장하는 방법 - bcrypt

Node.js와 MongoDB로 웹 페이지를 만들면서 비밀번호는 사실 String으로 만들어 두었었다.

이때까지 완벽하게 구현하는게 목표였지 보안에는 신경을 쓰지않았었다.

그러던 중 포프님의 해시와 암호화를 보고나서 암호를 PlainText로 저장하는건 자살행위나 다름없구나라고 깨닫고 비밀번호를 암호화 하는 방법을 찾아보았다.


서버에 패스워드를 저장하는 방식의 문제점들

보통 서버에 사용자의 Password를 두가지 방식으로 저장을한다.

1. Plain Text

2. 해시값

일단 첫번째 경우 보안의 중요성을 몰랐을때 저지르는 실수인데, 만약 이런 웹사이트에서 회원가입을 하는건 인터넷상에 내 정보를 올리는 것과 마찬가지니 이런 곳에서는 회원가입을 하지 않는 것이 좋다.

두 번째 방식은 원래의 값을 해시함수의 결과 값(Digest)으로 변환하므로 원본 메시지를 알아내기 힘드므로 좋은 선택이다.

그러나 해시함수의 문제점이 발생한다. 바로 Rainbow Attack이다.

Rainbow attack은 해시함수의 특성상 동일한 메시지가 동일한 해시 값(Digest)를 가지기 때문에, 공격자가 사람들이 가장 많이 쓰이는 Password의 해시 값들을 Table형식으로 만들어두는데 이 것을 Rainbow Table이라고 한다. 이 Table을 이용해서 패스워드를 탈취하는 방식을 Rainbow Attack이라고한다.


해시 함수 보완  - Salting

위에서 설명한 문제점을 해결하기 위해 Salt라는 임의의 바이트 문자열을 추가해서 해시 값을 만든다.

hash salt digest에 대한 이미지 검색결과

bcrypt - Password hashing function

bcrypt는 1999년에 발표한 해시 암호 알고리즘으로 애초에 Password 저장을 목적으로 설계되었다.

또한, OpenBSD에서 기본 암호 인증 메커니즘으로 사용되고있다.


설명이 길었다.

이제 Node js 코드로 설명하겠다.

User Schema

먼저 아래 코드는 User의 정보가 저장되는 MongoDB Schema다.

UserSchema.pre() 부분은 MongoDB에 저장되기 전에 진행되는 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
 
const UserSchema = new mongoose.Schema({
  id: { type: String, unique: true, require: true, trim: true },
  pwd: { type: String, require: true },
});
 
/* Hashing password */
UserSchema.pre('save'function(next) {
  const user = this;
  const saltFactor = 10;
  bcrypt.genSalt(saltFactor, (err, salt) => { // Salt 생성
    if (err) return next(err);
 
    bcrypt.hash(user.pwd, salt, (err, hash) => { // Hash생성
      if (err) return next(err);
      user.pwd = hash; // Hash값 pwd에 저장
      next();
    });
  });
});
 
module.exports = mongoose.model('user', UserSchema);
cs


생성

아래 함수는 ID를 생성하는 부분이다. 

현재 만드는 웹사이트는 회원가입은 필요하지 않고 Admin계정과 권한 설정한 ID만 필요하므로 이정도로 만들었다.

User.create()에서 MongoDB에 Collection을 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Create Admin account */
router.get('/create', (req, res, next) => {
  User.findOne({ id: 'test', pwd: 'test' }, (err, user) => {
    if (err) return res.status(500).json({ error: err });
    if (!user) {
      const userData = {
        id: process.env.USER_ID, // User ID
        pwd: process.env.USER_PW, // User PW
      };
      User.create(userData, (err, user) => {
        if (err) next(err);
        else return res.redirect('/login');
      });
    }
    return res.redirect('/login');
  });
});
cs


결과 값

pwd부분을 보면 $2b~$~로 저장되어있다.

앞 부분에 $~$는 어떤 포멧으로 Hash화 되어있는지 표시한다.


[참고자료]

NaverD2 - 안전한 패스워드 저장 : https://d2.naver.com/helloworld/318732

Wikipedia - bcrypt : https://en.wikipedia.org/wiki/Bcrypt

Password Authentication with Mongoose : http://devsmash.com/blog/password-authentication-with-mongoose-and-bcrypt

댓글