
프로젝트에서 회원가입 및 로그인 기능을 node.js crpyto 내장 모듈을 활용하여 암호화를 진행한 내용에 대해 정리했다
비밀번호를 암호화 하게 된 이유는 회원가입 후 사용자에게 입력받은 비밀번호를 있는 그대로 DB에 저장하면 DB가 해킹되는 순간 사용자의 비밀번호가 외부로 유출될 위험이 있기 때문이다
따라서 모든 회원가입 과정에서 비밀번호의 암호화 과정은 필수적이라고 생각했다
node.js 에서
crypto모듈은 암호화 및 해시 기능을 제공하는 모듈입니다
const crypto = require('crypto');
다음과 같이 crpyto 모듈을 불러와 사용하면 된다
암호화 방식은 크게 단방향 방식과 양방향 방식으로 구분된다
단방향 암호화는 암호화가 가능하지만 원래 비밀번호를 확인하는 복호화는 불가능하다. 주로 Hash 알고리즘으로 암호화를 진행한다
양방향 암호화는 암호화도 가능하고 복호화로 원래 비밀번호를 알아낼 수 있다. 주로 대칭키(비공개키),비대칭키(공개키) 방식을 활용하여 암호화를 진행한다
비밀번호는 사용자 본인만 확인이 가능해야하며 비밀번호를 잊어버려 복호화하는 과정에서 비밀번호 유출 위험 있을 수 있기 때문에 비밀번호를 재설정 하는 방식으로 기능을 구현한다
따라서 비밀번호는 단방향 암호화 방식을 사용하여 암호화만 가능하고 복호화는 불가능하게 설정한다
const join = (req,res)=>{
const {email,password} = req.body;
const salt = crypto.randomBytes(10).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password,salt,10000,10,'sha512').toString('base64');
let sql = 'INSERT INTO users (email,password,salt) VALUES (?,?,?)';
let values = [email,hashPassword,salt];
conn.query(sql,values,
(err,results)=>{
if(err){
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
}
crypto 모듈을 활용하여 비밀번호 암호화를 진행했다
먼저 Node.js의 crypto 모듈을 사용하여 10바이트 길이의 임의의 salt 값을 생성하고, 이를 base64 문자열로 변환하는 작업을 수행한다
다음 주어진 비밀번호와 salt를 사용하여 PBKDF2 (Password-Based Key Derivation Function 2) 알고리즘을 동기적으로 실행하고, 결과를 base64 문자열로 인코딩한다
이 때, salt는 비밀번호 해싱에 무작위성을 추가하여 레인보우 테이블 공격과 같은 공격을 방지하는 역할을 수행한다
const login = (req,res)=>{
const {email,password} = req.body;
let sql = 'SELECT * FROM users WHERE email=?';
conn.query(sql,email,
(err,results)=>{
if(err){
console.log(err)
return res.status(StatusCodes.BAD_REQUEST).end();
}
const loginUser = results[0];
const hashPassword = crypto.pbkdf2Sync(password,loginUser.salt,10000,10,'sha512').toString('base64');
if(loginUser && loginUser.password == hashPassword){
const token = jwt.sign({
email : loginUser.email,
password : loginUser.password
},process.env.PRIVITE_KEY,{
expiresIn : '5m',
issuer : 'master'
});
res.cookie("token",token,{
httpOnly : true
});
return res.status(StatusCodes.OK).json({
message : '로그인 완료되었습니다'
});
} else {
return res.status(StatusCodes.UNAUTHORIZED).json({
message : '아이디 또는 비밀번호가 잘못 입력되었습니다'
});
}
});
}
암호화된 비밀번호를 DB에 저장하고 DB에 저장된 비밀번호 값과 사용자가 입력한 비밀번호를 암호화한 값이 같다면 로그인이 되도록 구현했다
해쉬 함수는 같은 입력 값에 같은 출력값이 나오는 게 보장되어 암호화된 비밀번호 값이 서로 같다면 로그인이 성공적으로 이뤄지고 JWT가 사용자에게 전달된다
const passwordResetRequest = (req,res)=>{
const {email} = req.body;
let sql = 'SELECT * FROM users WHERE email = ?';
conn.query(sql,email,
(err,results)=>{
if(err){
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
const findUser = results[0];
if(findUser){
return res.status(StatusCodes.OK).json({
email : email
});
} else {
return res.status(StatusCodes.UNAUTHORIZED).end();
}
});
}
사용자가 비밀번호를 잊어버려 초기화 요청을 할 수 있기 때문에 다음 기능을 구현했다
이 기능에서는 사용자가 입력한 이메일 값과 DB에 저장된 이메일 값이 같다면 비밀번호 초기화가 가능하다고 확인할 수 있도록 기능을 구현했다
const passwordReset = (req,res)=>{
const {email,password} = req.body;
let sql = 'UPDATE users SET password=?, salt=? WHERE email=?';
const salt = crypto.randomBytes(10).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password,salt,10000,10,'sha512').toString('base64');
let values = [hashPassword,salt,email];
conn.query(sql,values,
(err,results)=>{
if(err){
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if(results.affectedRows==0){
return res.status(StatusCodes.BAD_REQUEST).end();
} else {
return res.status(StatusCodes.OK).json(results);
}
})
}
다음은 비밀번호를 초기화 하는 기능을 구현했다
이전 비밀번호 초기화 요청 기능에서 사용자의 비밀번호 초기화가 가능하다는 메세지를 받았기 때문에 새롭게 입력받은 비밀번호를 통해 비밀번호가 다시 암호화되어 DB에 저장된다
대부분의 서비스는 회원가입과 로그인 기능이 포함된다
그리고 개인 정보가 회원 가입을 통해 DB에 저장되게 되는데, 이 때 저장되는 개인 정보는 매우 민감한 정보이기 때문에 암호화가 필수적이다
암호화의 중요성에 대해 학습할 수 있었고 회원 정보를 더욱 안전하게 보호할 수 있는 방법이 더 무엇이 있을까에 대해 앞으로도 충분히 고민하고 구현할 예정이다
추가) 공부하면서 다음 모듈도 많이 사용한다고 하여 추가 공부를 진행할 예정이다 bcrypt