JAVA Programming/JDBC

[Node.js] 암호화 작업을 하기 위한 crypto 모듈

꾸준히개발하자 2022. 4. 7. 00:41

암호화

암호화되는 작업을 하기 위해서 모듈이 필요하다

npm install crypto --save 암호화 모듈을 설치한다

crypto 모듈은 사용자가 pwd를 입력하면 그 pwd에다가 salt라는 키를 붙여서 암호화 한다

 

 

 

Express 기본 모듈

//Express 기본 모듈
require("dotenv").config();
var express = require("express");
var http = require("http");
var path = require("path");
var serveStatic = require("serve-static");
var expressErrorHandler = require("express-error-handler");
var expressSession = require("express-session");
var mongoose = require("mongoose");

crypto(암호화)모듈

 

var crypto = require("crypto")
var database;
var UserSchema;
var UserModel;

 

몽고디비 데이터베이스 연결

//데이터베이스 연결
function connectDB(){	
	
	//데이터베이스 연결 정보
	var databaseUrl = "mongodb://localhost:27017/shopping";
	
	//연결
	mongoose.connect(databaseUrl);
	
	database = mongoose.connection;
	
	database.on("open",function(){
		// 스키마 모델 객체
		createUserSchema(); // 함수로 호출할것임	
	});
	
	database.on("error",console.error.bind(console,"몽구스 연결 에러..."));
	
	database.on("disconnected",function(){
		
		console.log("DB연결이 끊겼습니다 5초후 재연결 합니다");
		setInterval(connectDB(),5000);
	});
}

 

스키마 정의 한것을 함수로 뺼것임 그 사이에 Virtual를 넣으면 함수가 길어지므로 

 

function createUserSchema(){
	
	UserSchema = mongoose.Schema({
		id:{type:String,required:true,unique:true}, // 아이디에 들어가는 값은 문자고 required 반드시 필요하고 , 유의값 - > 프라이머리키가 됨
		hashed_password:{type:String,required:true}, // 암호화된 패스워드 이다. 그러므로 hashed_pwd로 바꿈
		// 암호화 + salt , salt키를 저장해야함 안하면 High문자를 저장하면 a123를 넣으면 high라는애를 가지고 와서 붙여서 암호화를 해야하는데 암호화된게 hashed_pwd로 들어가는데
		salt:{type:String,required:true},// 다음에도 123를 붙이는데 앞에 salt키가 있어야되므로 항상 보관되어 있어야 한다. 암호화시킬때 임의로 붙이는 키값이다 . 반드시 필요하므로 required : true
		name:{type:String},
		age:{type:Number,'default':20},
		created:{type:Date,index:{unique:false},'default':Date.now} // unique:false 똑같은값이 들어가도 상관없다 , 날짜를 안넣으면 오늘날짜
	
	
	});	
	
	UserSchema
		.virtual("pwd") // 암호는 뭘로 만들건가 password로 만듬 , password값으로 넣겠다
		.set(function(pwd){	
			this._password = pwd; // 패스워드를 넣을것이다. _password 외부변수이다. 바꾸면 안됨
			this.salt = this.makeSalt(); // 아래에서 만든 salt키값을 _salt에 넣을것이다.
			
			this.hashed_password = this.encryptPassword(pwd); // 암호화를 시키는 메소드이다 밑에 만들것임		
	})
	.get(function(){	
		return this._password; // 패스워드 반환값 이 값을 hashed_password:{type:String,required:true} 에 넣는다
	})

 

스키마 객체에 메소드를 추가 : method() 방법

 

UserSchema.method("makeSalt",function(){ // 메소드를 만들면서 makeSalt를 호출함
		
	//	return "hi"; // hi붙이고싶다 hi라는 문자가 this.makeSalt()에서 호출되는데 salt에 hi가 들어감
					 // db에 salt:{type:String,required:true} 들어감 
					 // hi 123 A , hi 456 B , hi 787 C
					 // 암호화할때 hi만 붙이게되면 패스워드를 암호화하는 방식이 너무 단순
					 // A라는 사람이 로그인할때 hi라고 하지않고 abc123
					 // B라는 사람이 접속하면 123456
					 // c라는 사람이 접속하면 265189
					 // 그 사람만의 고유 salt를 만드는 것이다.
				    // id pw salt
					// A 123 abc = XXX 암호화된 결과물
					// B 456 129
					// C 789 369
			// 즉 독자적인 salt키를 갖게 된다.
			// pw + salt 를  암호화된 pwd와 비교하게 된다 . 
			// 실제패스워드를 저장하지않고 암호화된 패스워드 저장함
			// 각자의 salt키도 저장해야 한다.
		
		// 지금은 시시각각 변하는 난수값을 넣어서 알아내기 쉽지 않다
		console.log("date : " + new Date().valueOf()); // 초에따라 일련숫자 12341242 나옴
		console.log("math : " + Math.random()); // 0.1234214242 매번바뀜
		return Math.round((new Date().valueOf() * Math.random())) + ""; // 랜덤함수를 그순간에 만들어서 뒤에 + 붙여서 문자가됨 그러면 패스워드랑 붙을수있다. String값이됨	
	})

 

★ 암호화 작업을 하는 곳

 

UserSchema.method("encryptPassword",function(inputPwd,inSalt){ // 암호화를 시키려면 입력되는 패스워드 inputPwd 를 넣음
													// 암호화 하려면 salt가 필요하므로 inSalt
													// 그러면 들어가는 패스워드와 salt 가 합쳐져서 암호화가 된다.
		
		// 이미 암호화가 되어있으면 로그인이 되어있으면 123 입력하면
		// 얘를 암호화 해서 기존에 저장되어있는 암호화된 데이터를 같냐 해서 비교작업을 해줘야 하다.
		
		if(inSalt) { // inSalt가 있으면
			// 암호화 작업
			return crypto.createHmac("sha1",inSalt) // sha1 암호화 용어인데 쉬바라고 읽는다. 1은 암호화하는 등급이다. 암호화하는 모듈로써 모든곳에서 다쓴다
				.update(inputPwd).digest("hex"); // hex 헥사라고 읽음 								
		} else {
			// 안주게 되면  this 자체적으로  salt키를가지고 있으면 그걸써라
			return crypto.createHmac("sha1",this.salt) // sha1 암호화 용어인데 쉬바라고 읽는다. 1은 암호화하는 등급이다. 암호화하는 모듈로써 모든곳에서 다쓴다
			.update(inputPwd).digest("hex");
			
		}
		
	})

 

123를 암호화해서 암호화된 데이터를 비교해야되는 메소드를 만들어야한다.
로그인할때 암호화된 PWD비교

 

 

UserSchema.method("authenticate",function(inputPwd,inSalt,hashed_password){ // 사용자가 입력할 패스워드가 필요 
		// inputPwd + insalt 가 들어오면 123high 해서 암호화한다.
		// 암호화된 데이터가 db에 저장되어있는 암호화된 데이터를 읽어내야 한다
		// 두개를 비교하는데 inputPwd , insalt 와 db에서 꺼내온 암호화된 패스워드 hashed_password가 필요하다
		
		
		if(inSalt) { // 설트키 가 있으면
			
			console.log("사용자 입력 pwd: " + inputPwd); // abc 
			console.log("암호화된 pwd: " + this.encryptPassword(inputPwd,inSalt)); // 암호화된 패스워드를 보여줄것이다. abc가 xxxxxxx
			console.log("DB에 저장되어 있는 pwd: " + hashed_password);// db에 저장되어있는 암호화된 패스워드  abc가 db에저장된 암호화된 데이터
			
			// a 123 + salt 와 db에서 읽어온것과 일치하면 true 반환
			return this.encryptPassword(inputPwd,inSalt)==hashed_password;  // 암호화된 시킨것과 db에 저장된것과 일치하나? true , false
			
			
			
		} else {  // 설트키 가 없으면 패스워드만 감
				  // 위에서 부르면 function 에 inSalt가 없으면 this.Salt 로 감
			
			console.log("사용자 입력 pwd: " + inputPwd); // abc 
			console.log("암호화된 pwd: " + this.encryptPassword(inputPwd,inSalt)); // 암호화된 패스워드를 보여줄것이다. abc가 xxxxxxx
			console.log("DB에 저장되어 있는 pwd: " + hashed_password);// db에 저장되어있는 암호화된 패스워드  abc가 db에저장된 암호화된 데이터
			
			return this.encryptPassword(inputPwd)==this.hashed_password;
		}

		
	});

 

스키마 객체에 메소드를 추가(방법: static(), method() )
로그인에서 사용
 findById는 메소드의 이름 뒤에 콜백함수 

static를 써서 메소드 

 

UserSchema.static("findById",function(id,callback){ // 아이디를 가지고 select 했을때 들어갔을때 무조건 작업이 끝났을때 결과가 콜백으로 간다.
		return this.find({id:id},callback); // id를 찾아라 찾던안찾던 callback함수 실행해라
	})
	
	
	
	// 전체 데이터 사용
	UserSchema.static("findAll",function(callback){ // findAll 메소드를 
		return this.find({}, callback); // 전체데이터는 조건이 없어서 {} 몽땅가져옴 
	})
	
	console.log("UserSchema 정의함.");
	
	//Model 정의
	// users2 새로운 컬렉션(테이블)이 만들어지면서 위의 5개의 데이터를 저장하게 된다.
	UserModel = mongoose.model("users3",UserSchema); // UserSchema 에 findByid , findAll 이 들어왔으니까 userModel.findByid , userModel.findAll 로 찾을수있다.
	
	console.log("UserModel 정의함.");
	
}

 

 

익스프레스 객체 생성

 

var app = express();

app.set("port",process.env.PORT||3000);

app.use(express.urlencoded({extended:false}));

app.use("/public",serveStatic(path.join(__dirname,"public")));

app.use(expressSession({
	
	secret:"my key",
	resave:true,
	saveUninitialized:true
	
}));

 

사용자를 인증하는 함수

 

var authUser = function(database,id,pwd,callback){
	
	console.log("authUser 함수 호출..");
			
	//아이디와 비밀번호 검색

	
	//id가 function(id,callback) 에서 find({id:id}로 찾아 오게되면서 결과가 callback함수가 에러면 err 잘나오면result 로 들어감
	// 즉 , 콜백함수는 id를 찾았을경우 실행된결과를 받음

	UserModel.findById(id, function(err,result){ 		// 아이디 패스워드 체크했었는데 이젠 id만 주면됨

		// 에러가 있으면 이걸 실행 
		if(err){
			callback(err,null);
			return;
		}
		
		//데이터가 있을경우
		if(result.length>0){	
			console.log("아이디와 일치하는 사용자 찾음")
			// 무조건 배열로 넘어옴
			
			// ._doc = 도큐먼트 DB 값을 비교
			
			
			// 암호화된 데이터 비교
			var user = new UserModel({id:id});
			
			var authenticated = 
									
				user.authenticate(pwd,result[0]._doc.salt,result[0]._doc.hashed_password); // 위에 메소드 UserSchema.method("authenticate",function(inputPwd,inSalt,hashed_password) 호출
											// pwd , salt키 = db암호화된 비교작업
			
			// authenticated 에는 true 아니면 fasle 가 들어가게됨
			
		
		} if(authenticated){ // 사용자가 입력한 pwd가 일치하나 ?  	
				console.log("비밀번호 일치함")
				callback(null,result); // 에러는 없으니 null ,데이터 있으니 result 는 넣어줌 	
			
		} else {
			console.log("비밀번호가 일치하지 않음");
			callback(null,null); // 에러없고 결과가 없으니 null , null
			
			}
						
		/*} else {
			
			console.log("아이디와 일치하는 데이터가 없습니다.");
			callback(null,null); // 에러없고 결과가 없으니 null , null
		
			
		}*/
		
	});
	
}

 

사용자를 추가하는 함수

 

var addUser = function(database,id,pwd,name,callback){
	
	console.log("addUser 함수 호출..");
	
	var users = new UserModel({"id":id, "pwd":pwd, "name":name});
	
	users.save(function(err,result){
		
		if(err){
			callback(err,null);
			return;
		}
		if(result){	
			console.log("사용자 추가..");
			
		}else{
			console.log("사용자 추가 실패..");
		}
		callback(null,result);			
	});	
};
// 라우터 객체 생성
var router = express.Router();

// 로그인 라우터
router.route("/process/login").post(function(req,res){
	
	console.log("/process/login 호출..");
	
	var id = req.body.id;
	var pwd = req.body.pwd;
	
	if(database){
	
		authUser(database, id, pwd, function(err,result){
			
			if(err) {throw err;}
			
			if(result){
				
				var userName = result[0].name;
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>로그인 성공</h1>")
				res.write("<div>아이디: " + id + "</div>");
				res.write("<div>이름: " + userName + "</div>");
				res.write("<br/><br/><a href='/public/login.html'>다시 로그인</a>");
				res.end();				
			}else{	
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>로그인 실패</h1>")				
				res.write("<div>아이디 또는 패스워드를 확인하세요</div>");
				res.write("<br/><br/><a href='/public/login.html'>다시 로그인</a>");
				res.end();
			}	
		});
	}else{	
		res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
		res.write("<h1>데이터베이스 연결 실패</h1>")				
		res.write("<div>데이터베이스를 연결하지 못했습니다.</div>");		
		res.end();		
	}	
});

 

 

사용자 추가 라우터

router.route("/process/addUser").post(function(req,res){
	
	console.log("/process/addUser 호출..");
	
	var id = req.body.id;
	var pwd = req.body.pwd;
	var name = req.body.name;
	
	if(database){
		
		addUser(database, id, pwd, name, function(err,result){
			
			if(err) {throw err;}
			
			if(result){
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>사용자 추가 성공</h1>")	
				res.end();			
				
			}else{		
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>사용자 추가 실패</h1>")	
				res.end();			
			}
		});
		
	}else{
		
		res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
		res.write("<h1>데이터베이스 연결 실패</h1>")				
		res.write("<div>데이터베이스를 연결하지 못했습니다.</div>");		
		res.end();
		
	}
});

 

사용자 리스트 라우터
폼버튼을 눌렀을때의 주소임 

 

router.route("/process/listUser").post(function(req,res){
	
	console.log("/process/listUser 호출됨")
	
	// db에서 데이터 가져옴
	
	if(database) { // 데이터베이스 객체가 있으면 연결해서 데이터 가져옴
		
		/*UserSchema.static("findAll",function(callback){
			return this.find({}, callback);
		})*/
		
		// 함수 호출하면됨 
		UserModel.findAll(function(err,result){  // 모든데이터 가져오는 메소드 static("findAll",  -> find{} 넘겨주는값이 필요없음 function(err,result) 로 처리 
			
			
			if(err) { // 에러가 발생하면 
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 리스트 조회중 에러 발생</h2>")				
				res.end();
				
				return; // 밑에 실행하지 않게 return 꼭 써줌 
			}
			
			if(result){ // 정상적으로 데이터를 가지고 왔을때
				
				// 모양 디자인 만드는 것임 
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 리스트</h2>")
				res.write("<div><ul>");
				
				
				for(var i=0; i<result.length; i++) {	
					var id = result[i]._doc.id; // 여러개니까 i번째 
					var name = result[i]._doc.name;
					var age = result[i]._doc.age;
				
					// ul안에 들어가있으니 li , number니까 # 하나 붙이고 i+1은 일련번호 , : 구분자 해주고 엔터치고 id + , + name + , + age + <li>
					res.write('<li>#'+ (i+1) + ' : ' 
							+ id + ", " + name + ' , ' + age + '</li>' )
				}
				
				
				res.write("</ul></div>");
				res.write("<br/><br/><a href='/public/listUser.html'>리스트</a>");
				res.end()
				
			} else {
				// 데이터가 없을 경우 
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 조회 실패</h2>")				
				res.end();
			}
		})  	
	}	
});

 

라우터 객체 등록

 

app.use("/",router);

var errorHandler = expressErrorHandler({
	
	static: {
		"404":"./public/404.html"
	}
	
});

app.use(expressErrorHandler.httpError(404));
app.use(errorHandler);

 

Express 서버 시작

 

http.createServer(app).listen(app.get("port"),function(){
	
	console.log("익스프레스 서버를 시작했습니다: " + app.get("port"));
	
	//DB연결 함수 호출
	connectDB();
	
});