POMOTODO : 암호화
2022. 1. 5. 17:04ㆍ프로그래밍/개인프로젝트
POMOTODO를 만들면서 작성한 코드를 복습하기 위해 작성하는 글입니다.
사용자의 정보를 받을 때 비밀번호를 직접 받으면 보안에 굉장히 취약하다는 문제가 있다
그래서 입력받은 비밀번호를 해쉬값으로 변경해서 서버에 저장해야한다
내생각 :
- hash를 사전에 검색하면 나오는 정의
1.'#'이라는 기호
2. '고기와 감자를 잘게 다져 섞어 요리하여 따뜻하게 차려 낸 것' 이라는 요리법
- 해시함수에 대한 정의
1. '하나의 주어진 출력에 대하여 이 출력으로 사상시키는 하나의 입력을 찾는 것이 계산적으로 불가능하고....,'
즉, 요리에 빗대서 쉽게 생각해보면 고기와 감자를 잘게다져서 섞으면 어떤재료들이 어떤 형태로 들어갔는지 사람은 알 수 없게 되는것처럼
입력값을 사람이 알 수 없는 문자들로 변환해주는 작업이라고 할 수 있다
즉, 요리에 빗대서 쉽게 생각해보면 고기와 감자를 잘게다져서 섞으면 어떤재료들이 어떤 형태로 들어갔는지 사람은 알 수 없게 되는것처럼
입력값을 사람이 알 수 없는 문자들로 변환해주는 작업이라고 할 수 있다
사용금지 - 단순 공부용)
md5를 사용하는 방법 (1. 모듈설치
npm istall md5
2. server에 md5 로드
let md5 = require('md5')
3. 사용예제 :
md5('입력값')을 출력해보면 입력값에 따라 암호화된 문자를 출력해준다
(사용자가 입력하는 값을 md5()로 묶어주고 서버와 대조해서 맞으면 로그인하도록 해준다)
md5('입력값')을 출력해보면 입력값에 따라 암호화된 문자를 출력해준다
(사용자가 입력하는 값을 md5()로 묶어주고 서버와 대조해서 맞으면 로그인하도록 해준다)
4. 사용상의 문제 :
하지만 암호화된 해쉬값을 입력값으로 변환해주는 프로그램을 사용하면 중요 정보가 노출될 수 있으므로
단독으로 사용하면 매우 위험하다
그리고 보안이 이미 뚫렸으므로 md5는 암호화로서의 가치가 없어졌다고 할 수 있다
하지만 암호화된 해쉬값을 입력값으로 변환해주는 프로그램을 사용하면 중요 정보가 노출될 수 있으므로
단독으로 사용하면 매우 위험하다
그리고 보안이 이미 뚫렸으므로 md5는 암호화로서의 가치가 없어졌다고 할 수 있다
salt
해쉬값변환을 통한 정보노출에 대한 해결책랜덤생성되는 암호화 키값인 salt와 '사용자가 입력한 비밀번호'를 더해서 '암호화된 해쉬값'을 만들면
해쉬값 변환 프로그램을 통해 알 수가 없다
해쉬값 변환 프로그램을 통해 알 수가 없다
db에 '암호화된 해쉬값','salt값'을 넣어두고 유저의 '입력값'+'salt값' = '암호화된해쉬값' 인지 검사하도록 구현한다
salt값을 생성해주는 pbkdf2를 사용하도록 해보자
pbkdf2 (Password-Based Key Derivation Function 2)
pbkdf2는 키 스트레칭 기법 (중첩암호화기법)을 사용하기 위해 만들어진 함수이다
1. 모듈설치
npm install pbkdf2-password
2. server.js에 코드작성
let bkfd2Password = require('pbkdf2-password')
let hasher = bkfd2Password();
hasher({password:'비밀번호'}, function(err, pass, salt, hash){
console.log(err, pass, salt, hash);
//콘솔로그로 찍으면 err = undefined, pass:입력한비밀번호값, salt: 랜덤생성, hash: 입력비밀번호+salt값에 대한 해쉬값 을 출력해준다
})
3. 회원가입부분과 로그인 부분에 코드를 추가하였다
회원가입app.post('/signupResult',function(req, res){
db.collection('users').findOne({id: req.body.loginId}, function(err,result){
if(result == null){
// console.log('아이디가 없음')
//서버에 아이디가 없으면 if, 있으면 else 출력 -> 없으면 가입을 진행해주고 있으면 얼럿창띄운다음에 회원가입페이지로 보내기
hasher({password: req.body.password}, function(err, pass, salt, hash){
// console.log(err, pass, salt, hash);
// err = undefined, pass:입력한비밀번호값, salt: 랜덤번호생성, hash: 입력비밀번호+salt값에 대한 해쉬값 을 출력해준다
// db의 컬렉션 만들어서 데이터 저장하기
db.collection('users').insertOne({ id : req.body.loginId, hashPassword : hash, saltPassword : salt, email : req.body.email, number : req.body.number, gender : req.body.gender,birthday : req.body.birthday, }, function(err, result){
console.log('db user create')
})
db.collection('pomodoro').insertOne({ id : req.body.loginId, content: '', contentHTML:''}, function(err, result){
console.log('db pomodoro create')
})
db.collection('todolist').insertOne({ id : req.body.loginId, todoList: '', todoListHTML:''}, function(err, result){
console.log('db todolist create')
})
db.collection('not-todolist').insertOne({ id : req.body.loginId, notTodoList: '', notTodoListHTML:''}, function(err, result){
console.log('db not-todolist create')
})
res.send("<script>alert('WELCOME !');location.href='/login';</script>");
})
}else{
// console.log('아이디가 있음')
res.send("<script>alert('this id is already in use.');location.href='/signup';</script>");
}
});
})
로그인
- login.ejs
<!--login.ejs-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
<link rel="manifest" href="favicon/site.webmanifest">
<link rel="mask-icon" href="favicon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<title>POMOTODO</title>
<link rel="stylesheet" href="login-signup.css" />
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script defer src="login.js"></script>
</head>
<body>
<div class="container">
<h1>log in</h1>
<div class="login-title-underline"></div>
<form method="post" name="loginForm" id="authForm" action="/loginPost">
<div class="form-list">
<input type="text" class="form-list" id="loginId" name="loginId" placeholder="username" minlength="5" maxlength="10" autocomplete='off' autofocus>
</div>
<div class="form-list">
<input type="password" class="form-list" id="loginPw" name="loginPassword" placeholder="password" minlength="5" maxlength="20" autocomplete='off'onkeydown="if(window.event.keyCode==13){printName()}">
</div>
<div class="id-check"><%= loginFeedback %></div>
<button type="button" class="form-list" id="form-login" onclick='printName()' >login</button>
<button type="button" class="form-list" id="login-toHome" onclick="location.href='/'">home</button>
<button type="button" class="form-list" id="login-toSignup" onclick="location.href='/signup'">sign up</button>
<button type="button" class="form-list" id="form-pomotodo" onclick="window.open('https://github.com/coqoa/POMOTODO/blob/main/README.md')">what is POMOTODO?</button>
</form>
</div>
</body>
</html>
- login-signup.css(login.ejs와 signup.ejs에 첨부하는 css파일)
/* login-signup.css */
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap");
/* font-family: 'Ubuntu', sans-serif; */
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+KR:wght@100;200;300;400;500;600;700&display=swap");
/* font-family: 'IBM Plex Sans KR', sans-serif; */
:root {
--backgroundColor: #f0f0f0;
--contentColor: #f5f5f5;
--blackShadow: #cfcfcf;
--whiteShadow: #f2f2f2;
--lightgrayShadow: #d9d9d9;
--bluetext: #221974;
--redtext: #ff8585;
--hoverredtext: #f75858;
--graytext: #c7c8ca;
--beigetext: #f5f5dc;
--greentext: #76c576;
}
body {
/* background-color: #e9f0fa; */
background-color: var(--backgroundColor);
}
.container {
/* background-color: #e9f0fa; */
background-color: var(--contentColor);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 350px;
height: 600px;
box-shadow: 7px 7px 10px var(--blackShadow), -4px -4px 5px var(--whiteShadow);
border-radius: 15px;
zoom: 80%;
}
/* #signupForm, */
/* 로그인폼 레이아웃 */
#authForm {
position: relative;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 로그인 폼 내 각 요소에 관한 코드 */
.form-list {
font-family: "Ubuntu", "IBM Plex Sans KR";
font-size: 17px;
font-weight: 400;
font-style: italic;
color: var(--bluetext);
padding: 5px;
margin: 3px;
border: 0;
border-radius: 1px;
text-align: center;
background-color: transparent;
}
h1 {
font-family: "Ubuntu", "IBM Plex Sans KR";
font-size: 40px;
font-weight: 500;
font-style: italic;
color: var(--bluetext);
position: relative;
top: 30px;
right: 85px;
text-align: center;
margin-bottom: 150px;
}
label {
position: relative;
top: 0px;
right: 100px;
}
/*------------------------------------------------ ⬆︎ login, signup공통적용 ------------------------------------------------ */
/* ⬇︎ 개별적용 */
/*------------------------------------------------ ⬇︎ login ------------------------------------------------ */
.login-title-underline {
position: relative;
top: -125px;
left: 45px;
height: 5px;
width: 95px;
border-bottom: 3px solid var(--bluetext);
}
#loginPw,
#loginId {
position: relative;
top: 20px;
width: 250px;
height: 40px;
border: 0;
margin-bottom: 30px;
background-color: white;
/* border: 1px solid white; */
border-radius: 15px;
box-shadow: inset 2px 2px 3px var(--blackShadow),
inset -2px -2px 2px var(--whiteShadow);
}
#loginPw {
top: 26px;
}
#form-login {
font-size: 20px;
font-weight: 500;
position: relative;
top: 25px;
margin-top: 20px;
width: 260px;
height: 50px;
color: var(--bluetext);
background-color: var(--contentColor);
box-shadow: 4px 4px 6px var(--blackShadow), -3px -3px 5px var(--whiteShadow);
border-radius: 15px;
}
#form-login:hover {
cursor: pointer;
color: var(--hoverredtext);
}
#form-login:active {
box-shadow: inset 2px 2px 3px var(--blackShadow), inset -2px -2px 2px #ffffff;
}
/* 플래시 메세지 */
.id-check {
position: absolute;
top: 210px;
font-family: "Ubuntu", "IBM Plex Sans KR";
font-size: 12px;
font-weight: 500;
font-style: italic;
color: var(--redtext);
}
#login-toHome,
#login-toSignup,
#form-pomotodo {
font-size: 15px;
position: relative;
color: var(--graytext);
top: 40px;
}
#login-toHome:hover,
#login-toSignup:hover,
#form-pomotodo:hover {
cursor: pointer;
color: black;
}
/*------------------------------------------------ ⬇︎ signup ------------------------------------------------ */
#signup-container {
height: 650px;
}
/* .form-list label {
color: gray;
} */
/* 라벨위치 미세조정 */
.label-id {
right: 128px;
}
.label-email {
right: 115px;
}
.label-number {
right: 106px;
}
.label-birthday {
right: 104px;
}
.signup-title {
top: 0px;
right: 70px;
}
.signup-title-underline {
position: relative;
top: -150px;
left: 42px;
height: 5px;
width: 130px;
border-bottom: 3px solid var(--bluetext);
margin-bottom: 40px;
}
.gender-label input[type="radio"] {
display: none;
}
.gender-label input[type="radio"] + span {
position: relative;
width: 50px;
top: 15px;
left: 101px;
display: inline-block;
background: none;
border: 1px solid lightgray;
border-radius: 10px;
padding: 0px 10px;
margin-bottom: 20px;
text-align: center;
height: 35px;
line-height: 33px;
font-weight: 500;
cursor: pointer;
color: lightgray;
z-index: 100;
}
.gender-label input[type="radio"]:checked + span {
border: 1px solid var(--bluetext);
background: none;
color: var(--bluetext);
}
#signupId,
#signupPassword,
#signupPasswordCheck,
#signupEmail,
#signupNumber,
#signupBirthday {
font-size: 14px;
position: relative;
width: 250px;
height: 20px;
background-color: white;
/* border: 1px solid white; */
margin: 3px;
border-radius: 7px;
border: 0;
box-shadow: inset 2px 2px 3px var(--blackShadow),
inset -2px -2px 2px var(--whiteShadow);
}
#form-signup {
font-size: 18px;
font-weight: 500;
position: relative;
top: -20px;
margin-top: 20px;
width: 260px;
height: 35px;
color: var(--bluetext);
background-color: var(--contentColor);
box-shadow: 4px 4px 6px var(--blackShadow), -3px -3px 5px var(--whiteShadow);
border-radius: 8px;
cursor: pointer;
}
#form-signup:hover {
color: var(--redtext);
}
#form-signup:active {
box-shadow: inset 2px 2px 3px var(--blackShadow), inset -2px -2px 2px #ffffff;
}
#signup-toHome,
#signup-toLogin {
font-size: 15px;
position: relative;
color: var(--graytext);
}
#signup-toHome {
top: -12px;
left: -40px;
}
#signup-toLogin {
top: -45px;
left: 39px;
}
#signup-toHome:hover,
#signup-toLogin:hover {
cursor: pointer;
color: black;
}
/* 회원가입 입력값체크*/
.validate-id {
display: block;
font-size: 12px;
position: absolute;
top: 60px;
left: 115px;
color: var(--redtext);
}
.message {
display: none;
font-size: 12px;
}
#password-message-match,
#number-message-count,
#birthday-message-count,
#email-check-match {
color: var(--greentext);
}
#password-message-unmatch,
#password-message-length,
#number-message,
#birthday-message,
#email-check-unmatch {
color: var(--redtext);
}
#password-message-match,
#password-message-unmatch,
#password-message-length,
#number-message,
#number-message-count,
#birthday-message,
#birthday-message-count,
#email-check-match,
#email-check-unmatch {
position: absolute;
top: 175px;
left: 26%;
}
#password-message-unmatch {
left: 30%;
}
#email-check-match,
#email-check-unmatch {
top: 250px;
left: 28%;
}
#number-message,
#number-message-count {
top: 320px;
left: 27%;
}
#birthday-message,
#birthday-message-count {
top: 385px;
left: 27%;
}
#number-message-count,
#birthday-message-count,
#email-check-match,
#password-message-match {
left: 49%;
}
@media screen and (max-width: 551px) {
.container {
zoom: 70%;
top: 350px;
}
}
- server.js
//server.js
// login버튼누르면 요청받는 post
app.post('/loginPost', passport.authenticate('local', { // 로컬방식으로 인증
failureRedirect : '/fail' ,
failureFlash : true
//실패시 /fail페이지로 이동시켜주세요
}), function(req, res){
req.session.save(function(){
// 성공시 redirect해서 홈페이지로 보내주기
res.redirect('/')
})
});
// 비밀번호 검사하는 passport 미들웨어 설정
//new LocalStrategy( { 설정 }, function(){ 아이디비번 검사하는 코드 } )
passport.use(new LocalStrategy({
usernameField: 'loginId', //사용자가 제출한 id가 어디 적혔는지(input의 name 속성값)
passwordField: 'loginPassword', //사용자가 제출한 pw가 어디 적혔는지(input의 name 속성값)
session: true, //로그인 후 세션을 저장할지 ?
passReqToCallback: false, //사용자가 입력한 id,pw외에 다른정보도 검증해보고 싶으면
}, function (inputId, inputPw, done) {
// console.log(inputId, inputPw) -> 사용자가 입력한 id/pw가 콘솔로그로 출력됨
db.collection('users').findOne({ id: inputId }, function (err, user) {
//입력한 id에 대한 정보를 결과에 담아옴
if (err) return done(err) //에러처리문법
if (!user) { //db에 없는 아이디일때?
return done(null, false, { message: 'ID does not exist' })
//done은 3개의 파라미터를 가질수 있음, 1: 서버에러, 2: 성공시 사용자db, 3:에러메시지
}
if(user){ // db에 아이디가 존재하면?
hasher({password:inputPw, salt: user.saltPassword}, function(err, pass, salt, hash){
// console.log(err, pass, salt, hash);
// err = undefined, pass:입력한비밀번호값, salt: 랜덤번호생성, hash: 입력비밀번호+salt값에 대한 해쉬값 을 출력해준다
// 입력비밀번호와 유저의 salt를 가져와서 hash로 만들고 그 해쉬값이 서버의 해쉬값과 일치하다면 로그인성공
if (hash == user.hashPassword) { // 인증완료 - 로그인
return done(null, user)
} else { // 비밀번호가 틀렸을 때
return done(null, false, { message: 'password incorrect' })
}
})
}
})
}));
'프로그래밍 > 개인프로젝트' 카테고리의 다른 글
POMOTODO : jQuery (ajax, sortable) (0) | 2022.01.06 |
---|---|
POMOTODO : DB (0) | 2022.01.05 |
POMOTODO : session, passport, serialize, deserialize (0) | 2022.01.05 |
POMOTODO : ejs템플릿 include 하기 (0) | 2022.01.05 |
POMOTODO : 회원가입 유효성검사 및 예외처리 (0) | 2022.01.05 |