노래하듯 이야기하고, 춤추듯 정복하라.

node.js 비밀번호 보안(Security Password) - md5, sha256, pbkdf2 본문

프로그래밍/node.js

node.js 비밀번호 보안(Security Password) - md5, sha256, pbkdf2

hyeoke 2018. 1. 2. 15:00

#서두

안녕하세요 모닝버드입니다. 오늘은 Node.js에서 인증을 구현할 때 사용자의 password를 안전하게 암호화하여 저장하고 관리하는 방법에 대해 공부해 보았습니다. 암호화 모듈은 여러가지가 있는데요. 필자가 공부한 모듈은 md5, sha256(sha512), pbkdf2 이 세가지다.


처음으로 공부한 것이 md5인데 이 모듈은 사용하지 않는 것이 좋다. 


http://www.md5online.org/

https://crackstation.net/


위의 사이트들을 통해 md5를 통해 암호화된 hash(해쉬)를 입력하면 손쉽게 복호화를 할 수 있기 때문에, md5는 보안상 거의 사용하지 않는다.


* 복호화 : 암호화의 반댓말, 암호를 해독하다. 풀다.


#사용가능한 암호화 모듈 - sha256(512), pbkdf2

1. sha256

터미널에서 설치하기 -> npm install sha256 --save

아래의 사진의 sha256 모듈을 통해 특정 문자열을 암호화 한 것이다. md5에 비해서는 길고 복잡해 보인다. 



특정문자열을 password라고 가정했을 때 암호화를 더욱 복잡하고 강력하게 하는 방법이 있다. 바로 salt값을 이용한 암호화 방법이다. 아래의 사진처럼 기존의 password에 salt값을 더하여 sha256모듈로 암호화를 진행하는 것이다. 



2. md5와 sha256의 문제점

이 두가지의 암호화 모듈은 전형적인 단방향 해시 함수의 문제점을 갖는다. 


- 인식가능성(recogniability)

동일한 메세지가 언제나 항상 동일한 암호화값을 갖는다면(2~3명의 사용자가 같은 암호화 값을 가질 때), 공격자들이 전처리(pre-computing)된 암호화 값을 많이 확보한 다음이 이를 탈취한 암호화값과 비교하여 결국에는 원본 메세지를 찾아낼 가능성이 있다. 필자가 공부한 내용을 설명을 보충하자면 이러한 암호화값들을 다이제스트라고 부른다.


그리고 공격자들이 수많은 다이제스트를 모아놓은 목록을 레인보우 테이블(rainbow table)이라고 하며 위에서 설명한 것과 같은 공격 방식을 레인보우 공격(rainbow attack)이라고 한다. 여러 사용자의 다이제스트가 같다면 레인보우 공격으로 인해 다수의 정보가 한꺼번에 탈취 될 수 있다.


- 속도(speed)

본래 해시함수는 패스워드를 암호화하여 저장하기 위해 만들어진 것이 아니라 짧은 시간에 다량의 데이터를 검색하기 위해 만들어졌다고 한다. 이와 같은 속성 때문에 해시함수의 빠른속도 처리로 인해 공격자들은 매우 빠른속도로 임의의 문자열로 만들어지 다이제스트와 해킹할 다이제스트를 비교할 수 있다.


이러한 단방향 해쉬함수들의 문제점을 보완한 것이 바로 pbkdf2이다. 구체적으로 어떤 문제점들을 어떤방식으로 보완했는지에 대해서는 필자도 공부가 더 필요하다. 일단 오늘은 pbkdf2의 사용에 대해서만 이야기 하겟다.


#pbkdf2 사용하기

터미널에서 설치하기 -> npm install pbkdf2-password --save


pbkdf2를 사용하면 아래의 사진과 같이 자동으로 랜덤한 salt값을 생성하고 salt값과 pwd값을 이용하여 강력한 hash값을 만들어 낸다. 이와 같이 hasher 함수를 실행할 때마다 랜덤한 salt값을 생성하기 때문에 여러명의 사용자의 password가 똑같다고 하더라도 그들의 다이제스트값을 달라질 수 밖에 없다. (인식가능성에 대한 문제 보완) 



#실습했던 코드 (Login, Legister)

//pbkdf2
var bkfd2Password = require('pbkdf2-password');
var hasher = bkfd2Password();

// login
app.get('/auth/login', (req, res) => {
  if(!req.session.displayName){
    res.render('login');
  } else {
    req.session.save(() => {
      res.redirect('/welcome');
    });
  }
});
app.post('/auth/login', (req, res) => {
  var myid = req.body.myid;
  var mypw = req.body.mypw;
  for(var i=0; i {
        if(hash === user.mypw) {
          req.session.displayName = user.displayName;
          req.session.save(() => {
            res.redirect('/welcome');
          });
        } else {
          res.render('notlogin');
        }
      });
    }
  res.render('notlogin');
});

// register
app.get('/auth/register', (req, res) => {
  res.render('register');
});
app.post('/auth/register', (req, res) => {
  hasher({password: req.body.mypw}, (err, pass, salt, hash) => {
    var user = {
      myid: req.body.myid,
      mypw: hash,
      salt: salt,
      displayName: req.body.displayName
    }
    users.push(user);
    req.session.displayName = req.body.displayName;
    req.session.save(() => {
      res.redirect('/welcome');
    });
  });
});



Comments