프로그래밍/Node.js

Node.js 생활코딩 정리 – Password Key Stretching 기법(PBKDF2 도입)

가카리 2016. 12. 4. 19:54
반응형

Security – Password Key Stretching 기법

 

입력한 패스워드의 다이제스트를 생성하고, 생성된 다이제스트를 입력 값으로 하여 다이제스트를 생성하고, 또 이를 반복하는 방법으로 다이제스트를 생성할 수도 있다. 이렇게 하면 입력한 패스워드를 동일한 횟수만큼 해시해야만 입력한 패스워드의 일치 여부를 확인할 수 있다. 이것이 기본적인 키 스트레칭 과정이다.

 

즉 암호화를 반복하는 것

 

PBKDF2(Password-Based Key Derivation Function)의 도입

 

PBKDF2는 salt를 적용하고 해시함수의 반복횟수를 임의로 선택할 수 있다.

 

https://www.npmjs.com/package/pbkdf2-password에서 보면

 

pbkdf2-password모듈을 사용해야 한다.

 

 

hasher 함수의 첫번째 인자는 객체를 받음. password 프로퍼티는 암호화하고싶은 비밀번호를 뜻함

 

그리고 콜백함수가 호출되는데 err는 에러메시지, pass는 사용자가 입력한 패스워드를 그래로 보여주는 정보

 

salt는 자동생성된 salt값,

 

npm install –save pbkdf2-password

 

 

node 를 실행시키고

var bkfd2Password = require("pbkdf2-password");

var hasher = bkfd2Password();

hasher({password:'111111', function(err, pass, salt, hash){

console.log(err, pass, salt, hash);

});

 

 

그러면 err, pass, salt값과 hash값을 볼 수 있다.

 

다시 한번 실행 시키면 salt값이 바뀌기 때문에 해쉬 값이 바뀐다.

 

 

 

예전에 했던 것처럼 sha256대신 pbkdf2 를 써서 만들어보자. 수정한 부분은 노란색으로 표시해둠

 

app_multi_user_file_pbkdf2.js

 

var express = require('express');

var session = require('express-session');//세션 사용을 위한 모듈

var bodyParser = require('body-parser');//POST 방식 전송을 위해서 필요함

var app = express();

var FileStore = require('session-file-store')(session);//session 파일 스토어를 위해 사용

var bkfd2Password = require("pbkdf2-password");//pbkdf2-password 모듈 사용

var hasher = bkfd2Password();

 

app.use(bodyParser.urlencoded({extended: false}));//미들웨어 등록부분

//resave 세션아이디를 접속할때마다 발급하지 않는다

app.use(session({

secret: '12312dajfj23rj2po4$#%@#',

resave: false,

saveUninitialized: true,

store:new FileStore()//sessions라는 디렉토리가 생김

}));

 

app.get('/auth/logout', function(req, res){

delete req.session.displayName;//세션 삭제

res.redirect('/welcome');

});

 

app.get('/welcome', function(req, res){

if(req.session.displayName){//값이 있으면 로그인 성공

res.send(`

<h1>Hello, ${req.session.displayName}</h1>

<a href="/auth/logout">logout</a>

`);

}else{//값이 없으면 로그인에 실패 혹은 로그인 안한사람

res.send(`

<h1>Welcome</h1>

<ul>

<li><a href="/auth/login">Login</a></li>

<li><a href="/auth/register">Register</a></li>

</ul>

`);

}

});

 

var users = [

{

username:'egoing',

password:'bXHtzdi7m+jclUkfYdqgGsm5nYpBBYrqUFjkP+wHFF/j+zFipSf2N8GZJ7eD0yp9T9ovLbnCjjtS/upvcIL8Tw2ffG0G6vnk1vQfG4qMmSyXM4Aug8xgzJxGalSCnbkMVQR5A8r22kKoeTQIjkQtMFisPwZQZSuknrreGuq9am4=',

salt:'rWfFWL8EZGoKGGbzryiu1ZJfkfhvBo0XMinpFrJLTy43EmOV2g9LXIL6385l5FerQAlVK1jXYNmFryKHREPXxQ==',

displayName:'egoing'

},

];

 

app.post('/auth/register', function(req, res){

var user = {

username:req.body.username,

password:req.body.password,

displayName:req.body.displayName

};

users.push(user);//입력한 값을 users배열 맨뒤에 추가함

//res.send(users);//한번 뿌려봄

req.session.displayName = req.body.displayName;

req.session.save(function(){

res.redirect('/welcome');

});

});

 

app.get('/auth/register', function(req, res){

var output = `

<h1>Register</h1>

<form action="/auth/register" method="post">

<p>

<input type="text" name="username" placeholder="username">

</p>

<p>

<input type="password" name="password" placeholder="password">

</p>

<p>

<input type="text" name="displayName" placeholder="displayName">

</p>

<p>

<input type="submit">

</p>

</form>

`;

res.send(output);

});

 

app.post('/auth/login', function(req, res){

var uname = req.body.username;//POST방식으로 보낸 값을 가져옴

var pwd = req.body.password;

 

for(var i=0; i < users.length; i++){//계정이 실제로 존재하는지 확인하는 루프

var user = users[i];

if(uname === user.username){

return hasher({password:pwd, salt:user.salt}, function(err, pass, salt, hash){//salt 이미 저장된 salt값을 넘겨줌

if(hash === user.password){//저장된 해쉬값과 만든 해쉬값이 같으면 인증 성공

req.session.displayName = user.displayName;//세션값을 저장해줌

req.session.save(function(){

res.redirect('/welcome');

});

}else{

res.send('who are you?<a href="/auth/login">login</a>');//이게 실행되면 사용자가 존재하지 않음

}

});

}

 

/*if(uname === user.username && sha256(pwd+user.salt) === user.password){//아이디와 패스워드 둘다 같으면

req.session.displayName = user.displayName;

return req.session.save(function(){//세션값이 세팅이 끝나면 콜백이 호출됨 return때문에 for문이 즉시 중지됨

res.redirect('/welcome');// welcome 페이지로 이동

});*/

 

}

});

 

app.get('/auth/login', function(req, res){

var output = `

<h1>Login</h1>

<form action="/auth/login" method="post">

<p>

<input type="text" name="username" placeholder="username">

</p>

<p>

<input type="password" name="password" placeholder="password">

</p>

<p>

<input type="submit">

</p>

</form>

`;

res.send(output);

});

 

app.get('/count', function(req, res){

if(req.session.count){//값이 있을때

req.session.count++;

}else{//처음 접속했을때 값이 없을

req.session.count = 1;//세션을 만듬

}

 

res.send('count : ' + req.session.count);

});

 

app.listen(3003, function(){

console.log('Connected 3003 port!!!');

});

 

실행 결과

로그인 아이디 egoing 패스워드 111111를 치면

다음과 같이 로그인이 잘된다.

 

이번에는 pbkdf2 방식으로 register를 하는 방법을 알아보자

 

app_multi_user_file_pbkdf2.js

 

위 소스에서 아랫부분만 수정한다

 

app.post('/auth/register', function(req, res){

hasher({password:req.body.password}, function(err, pass, salt, hash){

var user = {

username:req.body.username,

password:hash,//hash 대체함

salt:salt,//만든 salt값도 같이 저장함

displayName:req.body.displayName

};

users.push(user);//입력한 값을 users배열 맨뒤에 추가함

//res.send(users);//한번 뿌려봄

req.session.displayName = req.body.displayName;

req.session.save(function(){

res.redirect('/welcome');

});

});

});

 

실행 결과

welcome 페이지에서 Register를 해보자

다음과 같이 회원가입을 한다.

로그아웃 후 다시 로그인 하면 잘된다.

 

출처 : https://opentutorials.org/course/2136

반응형