bcrypt를 이용하여 로그인 암호 해싱하는 방법에 대해서 알아보도록 하겠습니다.
bcrypt
bcrypt는 애초부터 패스워드 저장을 목적으로 설계되었다. Niels Provos와 David Mazières가 1999년 발표했고, 현재까지 사용되는 가장 강력한 해시 메커니즘 중 하나이다.
bcrypt는 보안에 집착하기로 유명한 OpenBSD에서 기본 암호 인증 메커니즘으로 사용되고 있고 미래에 PBKDF2보다 더 경쟁력이 있다고 여겨진다. bcrypt에서 “work factor” 인자는 하나의 해시 다이제스트를 생성하는 데 얼마만큼의 처리 과정을 수행할지 결정한다.
“work factor”를 조정하는 것만으로 간단하게 시스템의 보안성을 높일 수 있다. 다만 PBKDF2나 scrypt와는 달리 bcrypt는 입력 값으로 72 bytes character를 사용해야 하는 제약이 있다.
출처 : d2.naver.com
필요한 사전 설치 패키지
$ npm install bcrypt-nodejs --save
기본 사용법
// 평문 -> 해싱
bcrypt.hash("keyword", null, null, function(err, hash) {
// 성공하면 hash로 변환된 코드를 받을 수 있습니다
});
// 비교
bcrypt.compare("keyword", hash, function(err, res) {
// "keyword"와 hash(해싱된 코드)를 비교하여 같으면 true 아니면 false를 반환합니다
});
실제 예제
회원가입
var express = require('express');
var router = express.Router();
var path = require('path');
var mysql = require('mysql');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
// DATABASE SETTING (Google Cloud SQL)
var connection = mysql.createConnection({
host : 'SQL DB IP',
port : 3306,
user : 'id',
password : 'pw',
database : 'db name'
});
connection.connect();
// Routing
router.get('/', function(req, res) {
var msg;
var errMsg = req.flash('error');
if (errMsg) msg = errMsg;
res.render('join.ejs', {'message' : msg});
});
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
done(null, id);
});
// 성공했을때 리다이렉트 시키는 부분
router.post('/', passport.authenticate('join-local', {
successRedirect: '/main',
failureRedirect: '/join',
failureFlash: true
}));
passport.use('join-local', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
connection.query('select * from user where email=?', [email], function (err, rows) {
if (err) { return done(err); }
if (rows.length) {
return done(null, false, {message: 'your email is already used'});
}
else {
bcrypt.hash(password, null, null, function(err, hash) {
var sql = {email: email, password: hash}; // 입력받은 평문을 hash로 바꿔서 넣어준다
connection.query('insert into user set ?', sql, function (err, rows) {
if (err) throw err;
return done(null, { 'email' : email, 'id' : rows.insertId });
});
});
}
})
}
));
module.exports = router;
그럼 다음과 같이 평문으로 설정한 부분이, 해싱이 되어서 DB에 들어가게 된다
로그인
var express = require('express'); // express 모듈 사용하기 위함
var app = express();
var router = express.Router();
var path = require('path');
var mysql = require('mysql');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
// DATABASE SETTING (Google Cloud SQL)
var connection = mysql.createConnection({
host : 'SQL DB IP',
port : 3306,
user : 'id',
password : 'pw',
database : 'db name'
});
connection.connect();
// Routing
router.get('/', function(req, res) {
var msg;
var errMsg = req.flash('error');
if (errMsg) msg = errMsg;
res.render('login.ejs', {'message' : msg}); // login.ejs 호출
});
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
done(null, id);
});
passport.use('login-local', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
connection.query('select * from user where email=?', [email], function (err, rows) {
if (err) return done(err);
if (rows.length) {
// 여기서 DB로부터 가져온 해싱된 부분과 입력받은 평문을 비교
bcrypt.compare(password, rows[0].password, function(err, res) {
if (res) {
return done(null, { 'email' : email, 'id' : rows[0].uid });
}
else {
return done(null, false, {'message' : 'Your password is incorrect'});
}
});
}
else {
return done(null, false, {'message' : 'Your login info is not found'});
}
})
}
));
router.post('/', function(req, res, next) {
console.log('login local');
passport.authenticate('login-local', function(err, user, info) {
if (err) {
res.status(500).json(err); // 500 : Server Error
}
if (!user) {
return res.status(401).json(info.message); // 401 : 권한없음
}
req.logIn(user, function(err) {
if (err) return next(err);
return res.json(user);
});
}) (req, res, next);
});
router.get('/kakao', passport.authenticate('login-kakao'));
router.get('/oauth/kakao/callback', passport.authenticate('login-kakao', {
successRedirect: '/main',
failureRedirect: '/'
}));
module.exports = router;