[bcrypt] Hashing의 보안을 더 높인 Salting Round

2020. 5. 17. 16:17컴퓨터언어/Database

728x90
반응형

데이터베이스에 회원정보를 보관할 때, MD5같은 해시함수를 사용하면 상당한 보안을 갖출 수 있다.

 

하지만 유저가 비밀번호를 단순하게 설정한다면 말이 달라진다.

왜냐하면 해시함수는 서로 같은 입력값에 대해 언제나 동일한 해시를 출력하게 때문에,

일명 "자주 사용되는 비밀번호"는 "자주 사용되는 해시"와 같은 말이 되고, 이는 해커가 해시 테이블만 가지고 있다면 다 뚫린다는 의미가 된다.

 

이렇게 유저들이 단순하게 비밀번호를 설정함으로써 바람직하지 않은 결과를 초래하는 것을 방지하기 위해 등장한 개념이 Salting이다.

Salting은 음식에 간을 치듯이, 유저가 어떤 비밀번호를 설정했든지 상관없이, 거기에 난수까지 추가하여 해시함수에 집어넣는 것이다.

즉 비밀번호의 복잡도를 키워 보안을 높이는 것이다.

*비밀번호가 길수록 크래킹하는 데 필요한 연산시간이 기하급수적으로 늘어난다.

 

그리고 DB에는 이 [Salt 값]과 [유저비밀번호+Salt값]의 해시가 함께 저장되며,

유저는 이후 로그인할 때마다 실제 비밀번호만 입력하면, 서버는 [입력한 비밀번호+저장되어있는 Salt값]의 해시값을 DB와 비교하여 입장여부를 결정하게 된다.

 

하지만 엄청 빠르면서도 저렴해지는 컴퓨팅 자원(무어의 법칙)으로, 해킹의 마저 빨라진다면 Salting을 무력화시킬 것처럼 보인다.

그래서 DB에는 기존 Salt를 적용하여 나온 결과 해시에다가, 다시 동일한 Salt을 적용하여 한번 더 결과 해시를 도출하고, 또 이걸 계속 반복시켜 해킹 자체를 힘들게 만들 수 있다.

이 반복 횟수를 Salting Round라고 하며, DB에는 [Salt값]과 Salting Round만큼 반복하여 나온 [최종 해시]를 저장한다.

 

이 모든 것을 가능케 하는 것이 bcrypt 모듈이다.

https://www.npmjs.com/package/bcrypt

 

bcrypt

A bcrypt library for NodeJS.

www.npmjs.com

만약 OSX에서 아래와 같은 에러가 뜬다면, npm i -g node-gyp부터 해보면 될 것이다.

bcrypt로 해시를 만드는 함수는 다음과 같으며, 해당 코드는 회원가입을 담당하는 HTML의 Form태그의 POST 메서드에서 사용하면 된다.

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

bcrypt.hash() 함수는 [유저가 실제 입력한 비밀번호], [Salting Round], [콜백함수] 인자를 차례대로 가지며,

실제입력 비밀번호는 body-parser로 접근하면 되고, Salting Round는 상수처리해서 넣으면 된다.

그리고 콜백함수는 위 bcrypt.hash()를 구동하면서 나오는 결과 해시(hash) 또는 에러(err)를 바로 핸들링하도록 도와주는 내부 함수이다.

즉 콜백함수 안에서 DB에서 처리할 항목들을 hash 인자를 활용하면 완료된다.

// 회원가입

app.route("/register")
  .get(function (req, res) {
    res.render("register")
  })

  .post(function (req, res) {
    bcrypt.hash(req.body.password, saltRounds, function(err, hash) {
      // Store hash in your password DB.
      const newUser = new User({
        email: req.body.username,
        password: hash
      });
      
      newUser.save(function (err) {
        if (err) {
          console.log(err);
        } else {
          res.render("secrets");
        }
      });
    });
  });

비밀번호를 qwerty, Salting Round를 10으로 설정했을 때의 해시

 

다음으로 로그인을 처리하려면 다음 함수가 필요하다.

// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
    // result == true
});

첫번째 인자로는 로그인 폼에서 유저가 실제 입력한 텍스트(비밀번호)를 body-parser로 불러오면 되고,

두번째 인자로는 DB에 저장되어 있는 유저의 해시를 불러온다. 이때 DB에 저장되어 있는 유저의 해시에 접근한다는 것은, 이 compare() 함수가 find()같이 mongoose의 READ 함수 내에 존재하여 아이디 같은 필드로 이미 1차 분류를 했어야 함을 의미한다.

세번째 인자로는 콜백함수를 활용하여 "유저가 방금 입력한 비밀번호의 해시"와 "DB상 이미 저장된 해시"를 비교하여 일치여부에 따른 명령을 분기하면 된다.

app.route("/login")
  .get(function (req, res) {
    res.render("login")
  })

  .post(function (req, res) {
    const userName = req.body.username;
    const password = req.body.password;
    User.findOne({email: userName}, function (err, result) {
      if (err) {
        console.log(err);
      } else {
        if (result) {
          // Load hash from your password DB.
          bcrypt.compare(password, result.password, function(err, result) {
            // result == true
            if (result) {
              res.render("secrets")
            }
          });
        }
      }
    })
  })

 

728x90
반응형