728x90
목차
032
암호화
- 암호화의 종류
- 단방향 암호화
- 양방향 암호화
- 비대칭형 암호화
- 대칭형 암호화
단방향 암호화
- 복호화할 수 없는 암호화 방법
- 원본 값을 알 수 없다.
- 복호화?
- 암호문을 원본으로 변경해주는 것이다.
- 비밀번호를 만들 때 사용하고 원본의 값을 알아낼 수 없기에 안전하다.
- 다만, 몇 가지 알고리즘은 뚫려 MD5, SHA1, SHA-180은 사용하면 안된다.
- 원래의 값을 알 수 없도록 복잡한 알고리즘으로 암호화를 시켜주기 때문에 원본 값을 복호화 할 수 없는 것
양방향 암호화
- 암호화할 때 사용하는 키와 복호화할 때 사용하는 키의 동일성에 대한 기준으로 구분한다.
- 비대칭형 암호화
- 대칭형 암호화
- 키가 있으면 해당 키로 암호화된 문자열을 복호화할 수 있다.
- 하지만 웹에서는 암호화된 문자열을 서버에서 클라이언트로 보낼 때 해당 키도 같이 보내주어야 한다.
- 때문에 클라이언트에서 어떤 일이 일어날지 몰라 해당 키를 클라이언트에게 보내줄 수 없다.
단방향 암호화 방법
- 가장 간단한 방법으로 해쉬 함수를 사용하는 방법이 있다.
- 해쉬함수?
- 같은 입력 값에 같은 출력값이 나오는게 보장되지만, 출력 값으로 입력 값을 유추할 수 없는 것을 의미한다.
- 아래와 같은 예시를 확인하면, 사용자가 입력한 비밀번호를 넘겨서 데이터베이스 저장해 놓는다면 실제 데이터베이스가 노출되어도 입력값을 알 수 없어 보안에 안전하다.
입력 값 | 출력 값 |
qwe | fhklqfnn83jrnlfni4gn492nnlnb29b24p244nf94m2p9fm2 |
123 | 89uwfnibm4m2f9b2794n208nv4f82j8rj9fjn04v4o42jf04 |
- 단방향 암호화는 Hash 알고리즘을 사용한다.
- 알고리즘을 통해 데이터를 고정된 크기의 고유한 값으로 바꿔주는 것이다.
- 예를 들어, Hash 알고리즘에서 길이를 6으로 설정했다면,
- 123456 => fg4fgl
- 3245 => 3njn45
- 이처럼 길이가 달라도, 6자의 비밀번호로 변경된다.
Hash 암호화
- 단방향 암호화 기술이다.
- 암호화가 진행되기 전 문장을 암호화된 문장으로 변경해준다.
- Hash 알고리즘은 종류가 다양하고, 모든 사람에게 공개되어 있다.
- Hash 알고리즘마다 Hash 길이가 다르고 이미 보안이 뚫린 해쉬가 다수 존재한다.
- 따라서 MD5, SHA1, SHA-180은 사용하면 안된다.
- Hash 알고리즘은 특정 입력에 대해 항상 같은 Hash 값을 리턴한다.
- Hash함수는 속도가 빠르다. 이 속도는 해커가 암호화된 문장을 해석하는데 유리하게 작용한다.
- 해커는 가능한 모든 입력 값에 대한 출력 값을 정리해놓고 정리해놓은 출력 값과 실제 데이터베이스에 저장되어 있는 값을 일일히 비교하는 작업으로 비밀번호를 탈취할 수 있기 때문이다.
- 이렇게 입력값에 대한 출력값을 저장해 놓은 것을 " 레인보우 테이블 "이라 한다.
Hash 구문
- createHash(algorithm[,options]): 사용할 해시 알고리즘을 입력한다.
- SHA256, SHA 512등의 알고리즘을 입력하여 사용한다.
- SHA256: 데이터를 256bit (32byte)의 고정 크기 해시 값으로 변환해주는 알고리즘이다.
- update(data[,inputEncoding]): 변환할 문자열을 입력한다.
- digest([encoding]): 인코딩할 알고리즘을 넣어준다. 변환된 문자열을 반환한다.
- base64, hex, latin1이 주로 사용된다.
- base64: 직역 시, 64진법 이라는 뜻이다. 8비트 이진 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식이다.
- hex: 16진수의 문자열로 변환한다.
- base64, hex, latin1이 주로 사용된다.
crypto
암호화의 종류
SHA
- 미국 국가안보국(NSA)가 1993년에 처음으로 설계해 미국 국가 표준으로 지정된 알고리즘이다.
- Hash 함수를 적용한 단방향 암호화 알고리즘으로 복호화가 불가능하다.
- GPU를 이용한 연산속도가 매우 빠르기 때문에 비밀번호 암호화에 권장되지 않는다.
- GPU속도가 빠를수록 공격자의 하드웨어를 통한 오프라인 brute force에 더 취약하다.
- SHA의 종류
- SHA-1
- SHA-2
- SHA256, SHA384, SHA512
- SHA-3
- SHA3-256, SHA3-384, SHA3-512 등
PBKDF2
- 해시함수의 컨테이너 역할을 한다.
- 검증된 해시함수만을 사용한다.
- 해시함수와 salt를 적용 후 해시함수의 반복횟수를 지정하여 암호화 할 수 있다.
- 가장 많이 사용되는 함수이다. ISO 표준에 적합해 NITS에서 승인된 알고리즘이다.
pbkdf2_hmac(해시함수(sha256..), password, salt, iteration, DLen)
const createHash = (salt,pw)=>{
return new Promise((resolve, reject)=>{
crypto.pbkdf2(
pw, // 해싱할 값을 문자열로 넣어주고 전달
salt, // salt 값
165165, // 키 스트레칭 반복 횟수. 반복횟수가 많아질수록 이렇게 암호가 되는데 시간도 오래 걸린다.
64, // 해시 값의 바이트. 64byte
"sha256", // 해시화 알고리즘
(err, hash)=>{
if(err) reject(err);
resolve(hash.toString("hex"));
}
)
})
}
- 패스워드 기반 키 생성을 볼 수 있는 사이트이다.
PBKDF2 테스트
패스워드기반 키생성 (PBKDF2) 사용자가 입력하는 패스워드를 직접 비밀키로 사용하는 것은 고정된 키를 사용하게 되어 사전공격 등의 방법이 가능하므로 보안성에 문제가 많다. 이를 해결하기
cris.joongbu.ac.kr
Bcrypt
- Blowfish 암호를 기반으로 설계된 암호화 함수이며, 현재까지 사용중인 가장 강력한 해시 메커니즘이다.
- .NET 및 Java를 포함한 많은 플랫폼과 언어에서 사용할 수 있다.
- 반복횟수를 늘려 연산속도를 늦출 수 있으므로 연산능력이 증가하더라도 brute-forece 공격에 대비할 수 있다.
Scrypt
- 오프라인 brute forece 공격에 대해 더 강력하지만, 많은 메모리와 CPU를 사용한다.
- OpenSSL 1.1 이상을 제공하는 시스템에서만 작동한다.
- 여러 언어의 라이브러리로 제공된다.
- 다른 암호기반 KDF에 비해 많은 양의 메모리를 사용하도록 설계되었다.
- 하드웨어 구현을 하는데 크기와 비용이 훨씬 더 비싸기 때문에, 주어진 자원에서 공격자가 사용할 수 있는 병렬처리의 양이 한정적이다.
crypto 모듈을 이용하여 암호화 제작
#. 폴더의 경로는 다음과 같다.
1. app.js_사용할 모듈을 불러온다.(crypto, express, path, mysql2, ejs)
const crypto = require("crypto");
const express = require("express");
const path = require("path");
const mysql2 = require("mysql2/promise");
2.app.js_salt값을 생성하는 함수를 생성한다.
- 해당 함수를 이용하여 회원가입 시, 모든 계정에 salt값이 들어가게 된다.
// salt 값을 만들어주는 함수
const createSalt = ()=>{
// 암호화에 시간이 좀 걸리기 때문에
return new Promise((resolve, reject)=>{
crypto.randomBytes(64,(err,result)=>{
if(err) reject(err);
// 실패[] 시 err 객체 reject메서드로 반환
// 성공하면 resolve 메소드로 결과를 16진수로 변환해서 반환
resolve(result.toString("hex"));
})
})
}
3. app.js_Hash를 생성하는 함수를 만든다.
- crypto의 'pbkdf2' 메소드를 사용하여 키 스트레칭을 적용한다.
- 키 스트레칭 기법은 아래의 bcrypt에서 자세히 설명하도록 한다.
const createHash = (salt,pw)=>{
return new Promise((resolve, reject)=>{
crypto.pbkdf2(
pw, // 해싱할 값을 문자열로 넣어주고 전달
salt, // salt 값
165165, // 키 스트레칭 반복 횟수. 반복횟수가 많아질수록 이렇게 암호가 되는데 시간도 오래 걸린다.
64, // 해시 값의 바이트. 64byte
"sha256", // 해시화 알고리즘
(err, hash)=>{
if(err) reject(err);
resolve(hash.toString("hex"));
}
)
})
}
4. app.js_서버 인스턴스를 생성한다.
const app = express();
5. app.js_body객체 사용 / view엔진 경로설정 / view엔진 ejs사용 설정
- body객체 사용
app.use(express.urlencoded({extended:false}));
- view엔진 경로설정
app.set("views", path.join(__dirname,"page"));
- view엔진 ejs사용 설정
app.set("view engine", "ejs");
6. app.js_mysql을 연결한다.
const mysql = mysql2.createPool({
user : "root",
password : "000000",
database : "test5",
multipleStatements : true
})
7. app.js_데이터베이스의 테이블을 초기화 한다.
// 테이블 초기화
const usersInit = async ()=>{
try {
await mysql.query("SELECT * FROM users");
} catch (error) {
await mysql.query("CREATE TABLE users(id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(20), user_pw VARCHAR(128), salt VARCHAR(128))")
}
}
usersInit();
8. app.js_루트경로('/')의 페이지와 ('/login')경로의 페이지를 render한다.
app.get('/',(req,res)=>{
res.render("join");
})
app.get('/login',(req,res)=>{
res.render("login");
})
9. app.js_회원가입 페이지에서 salt와 hash생성 함수를 이용하여 계정을 생성한다.
- users 테이블의 salt 컬럼에 넣을 salt값을 생성하고, pw 컬럼에 들어갈 hash 값을 생성한다.
app.post("/join",async(req,res)=>{
const {user_id, user_pw} = req.body;
const salt = await createSalt();
const hash = await createHash(salt, user_pw);
await mysql.query("INSERT INTO users(user_id,user_pw,salt)VALUE(?,?,?)",[user_id,hash,salt])
res.redirect("/login")
})
10.app.js_로그인 페이지에서 계정정보를 비교하여 로그인을 진행한다.
- 로그인 시도 시, 먼저 로그인창에서 입력한 id값을 기준으로 테이블에서 계정을 찾는다.
- 계정이 없을 경우, 유저가 없다는 에러메시지가 뜨고
- 있을 경우, 아래의 검사과정을 거치게 된다.
- 반환 받은 계정 값의 salt 값과, 로그인창에서 입력한 비밀번호로 제작한 salt값을 비교한다.
- 회원가입 시 입력했던 비밀번호와 현재 입력한 비밀번호가 같다면 hash값도 동일한 값이 나올것이다.
- 따라서, 두 값이 동일하면 로그인을 성공시키고
- 두 값이 동일하지 않다면 비밀번호가 틀렸다는 메시지를 보낸다.
- 반환 받은 계정 값의 salt 값과, 로그인창에서 입력한 비밀번호로 제작한 salt값을 비교한다.
app.post("/login", async(req,res)=>{
const {user_id, user_pw } = req.body;
const [result] = await mysql.query("SELECT * FROM users WHERE user_id = ?", [user_id]);
if(result[0]?.salt){
const salt = result[0].salt;
const hash = await createHash(salt, user_pw);
if(hash == result[0].user_pw){
res.send("로그인 됨")
}
else{
res.send("비밀번호 틀렸음")
}
}
else{
res.send("유저 없음")
}
})
11. page_ join.ejs와 login.ejs의 코드이다.
- join.ejs
<body>
<form action="/join" method="post">
<label for="">아이디</label> <br>
<input type="text" name="user_id"> <br><br>
<label for="">비밀번호</label> <br>
<input type="text" name="user_pw"> <br><br>
<button>가입하기</button>
</form>
</body>
- login.jes
<body>
<form action="/login" method="post">
<label for="">아이디</label> <br>
<input type="text" name="user_id"> <br>
<label for="">비밀번호</label> <br>
<input type="text" name="user_pw"> <br>
<button>로그인</button>
</form>
</body>
bcrypt
- 키(key)방식의 대칭형 블록 암호에 기반을 둔 암호화 해시 함수이다.
- 레인보우 테이블 공격을 방지하기 위해 salt와 키 스트레칭을 적용한 대표적인 예이다.
- 보안에 집착하기로 유명한 OpenBSD에서 사용하고 있다.
- 반복횟수를 늘려 연산속도를 늦출 수 있기 때문에 연산능력이 증가해도 공격에 대비할 수 있다.
구조
$2b$12$76taFAFPE9ydE0ZsuWkIZexWVjLBbTTHWc509/OLI5nM9d5r3fkRG
\/ \/ \____________________/\_____________________________/
Alg Cost Salt Hash
- $2a$[cost]$[salt][hash]
- $2a$: 해시 알고리즘 식별자. 고정값이다.
- [cost]: (ex. 12 = 2^12) Cost Factor로 키 스트레칭(Key Stretching)의 횟수이다.
- 기본적으로 많이 사용하는 횟수가 10이다. 이 수보다 많으면 많이 느려질 수 있다.
- [salt]: 인코딩 된 salt값이다. 알고리즘에서 문자열의 일부분을 salt값으로 사용한다.
- ex. 76taFAFPE9ydE0ZsuWkIZe
- 16byte 크기의 salt이며, Base64로 인코딩된 22개의 문자이다.
- [hash]: 비밀번호와 salt값을 합하고 해시해서 인코딩된 값이다.
- ex. xWVjLBbTTHWc509/OLI5nM9d5r3fkRG
- 24Byte의 해시 값, Base64로 인코딩된 31개의 문자이다.
Salt
- 복호화를 방해하기 위해 단방향 암호화시 소금을 뿌려 해커가 복호화 하는 것을 방해하는 방법이다.
- 입력으로 들어가는 비밀번호에 추가 문자열을 덧붙이는 방법이다.
- 따라서, 유저1과 유저2의 비밀번호 입력 값이 같더라도 발급된 해쉬 값을 달라, A의 정보가 탈취 당했어도, B의 정보는 안전한 것이다.
키 스트레칭 기법 (Key Stretching)
- 키 스트레칭 기법은 salt와 password를 해시함수에 넣는 과정을 반복해 해커가 복호화 하는 것을 귀찮게 만드는 방법이다.
- 따라서, 출력 값을 아주 느리게 산출될 수 있도록 한다.
brypto 모듈을 이용하여 암호화 제작
#. 폴더의 경로는 다음과 같다.
1. app.js_사용할 모듈을 불러온다.(bcrypt, express, path, mysql2, ejs)
const express = require("express");
const path = require("path");
2. app.js_body객체 사용 / view엔진 경로설정 / view엔진 ejs사용 설정
- body객체 사용
app.use(express.urlencoded({extended:false}));
- view엔진 경로설정
app.set("views", path.join(__dirname,"page"));
- view엔진 ejs사용 설정
app.set("view engine", "ejs");
- 서버 인스턴스 생성
const app = express();
3. config.js_mysql을 연결한다.
const mysql2 = require("mysql2/promise");
const mysql = mysql2.createPool({
user : "root",
password : "000000",
database : "test7",
multipleStatements : false
})
module.exports = mysql;
4. usersModel.js_mysql에 적용시킬 쿼리문을 작성한 함수를 설정한다.
- usersInit: 테이블 생성 함수
exports.usersInit = async()=>{
try {
await mysql.query("SELECT * FROM users");
} catch (error) {
await mysql.query("CREATE TABLE users(id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(20), user_pw VARCHAR(128))")
}
}
- userSelect: 계정 선택 함수
exports.userSelect = async(user_id)=>{
console.log(user_id,'믿지안아');
try {
const [result] = await mysql.query("SELECT * FROM users WHERE user_id = ?", [user_id]);
console.log("result",result)
return result[0];
} catch (error) {
console.log(error);
}
}
- userInsert: 계정 추가 함수
exports.userInsert = async(user_id,user_pw)=>{
try {
// 일단 중복되는 아이디인지 확인을 먼저 하자
const [user] = await mysql.query("SELECT * FROM users WHERE user_id = ?", [user_id])
if(user.length != 0){
// 이미 존재하는 아이디
let err = new Error("중복 아이디임");
console.log(err)
return err;
}
// 중복되지 않았으면 회원가입 정상적으로
await mysql.query("INSERT INTO users(user_id, user_pw)VALUES(?,?)",[user_id,user_pw]);
} catch (error) {
console.log(error);
}
}
5. index.js_' usersModel.js '에서 작성한 함수들을 불러오고 내보낸다.
const { usersInit, userInsert, userSelect } = require("./usersModel");
usersInit();
module.exports = { userInsert, userSelect };
6. usersController.js_controller에서 작성된 함수를 불러오고 사용할 모듈을 가져온다.
const { userInsert, userSelect } = require("../models");
const bcrypt = require("bcrypt");
7. usersController.js_hash를 생성하는 함수를 설정하고 검증을 할 수 있는 함수를 설정한다.
- createHash: hash값을 생성하는 함수이다.
const createHash = (password)=>{
return new Promise((resolve,reject)=>{
// hash메소드로 해시값을 만들어 줄 수 있다.
bcrypt.hash(password, 10, (err, data)=>{
if(err) reject(err);
resolve(data);
})
})
}
- compare: 문자열과 해시 값을 전달해주고 매개변수로 검증 결과를 확인한다.
- 검증결과를 true, false값으로 반환한다.
const compare = (password, hash)=>{
return new Promise((resolve, reject)=>{
// compare 메소드를 사용해서 문자열과 해시 값을 전달해주고 매개변수로 검증 결과를 확인한다.
bcrypt.compare(password, hash, (err,same)=>{
// if(err) reject(err);
resolve(same);
})
})
}
8. usersController.js_회원가입을 할 수 있는 함수를 설정한다.
- 요청한 ejs의 body에서 받은 비밀번호 값을 매개변수로 넣어 hash를 생성한다. 생성된 hash를 데이터베이스의 pw값으로 넣는다.
//회원가입
exports.Signup = async(req,res)=>{
const {user_id, user_pw} = req.body;
try {
const hash = await createHash(user_pw);
console.log("해시",hash)
await userInsert(user_id, hash);
res.redirect('/login');
} catch (error) {
console.log(error)
}
}
9. usersController.js_로그인을 할 수 있는 함수를 설정한다.
- 요청한 ejs에서 받은 아이디 값으로 데이터베이스에서 계정을 찾아 data에 반환한다.
- 첫 번째 if문에서 data에 body에서 입력한 id값이 없으면, 아이디가 없다는 화면이 뜨도록 한다.
- 있다면, 'compare'검증 함수로 데이터베이스의 pw와 사용자가 body에서 입력한 pw의 값이 동일한지 확인한다.
- 두 번째 if문에서 검증 결과가 false로 리턴되면 비밀번호가 틀렸다는 화면이 뜨도록 한다.
- 'compare'함수에서 검증 결과가 true로 리턴되면 로그인을 진행한다.
//로그인
exports.Login = async(req,res)=>{
const { user_id, user_pw } = req.body;
try {
const data = await userSelect(user_id);
console.log("데이터",data);
console.log("유저아이디",user_id);
if(!data?.user_id){
return res.send("아이디 없음")
}
const compare_pw = await compare(user_pw, data.user_pw);
console.log(compare_pw);
if(!compare_pw){
return res.send("비밀번호 틀림");
}
res.send("로그인됨")
} catch (error) {
console.log(error)
}
}
10. joinRouter.js_url을 '/join'으로 접속 시 join.ejs 페이지가 뜨게 한다. post요청을 받으면 usersController.js의 Singup 함수를 실행한다.
const router = require("express").Router();
const {Signup} = require("../controllers/usersController");
router.get('/',(req,res)=>{
res.render("join");
})
router.post('/',Signup);
module.exports = router;
11. loginRouter.js_url을 '/login'으로 접속 시 login.ejs 페이지가 뜨게 한다. post요청을 받으면 usersController.js의 Login 함수를 실행한다.
const router = require("express").Router();
const { Login } = require("../controllers/usersController");
router.get('/',(req,res)=>{
res.render('login');
})
router.post('/',Login);
module.exports = router;
12. page_ join.ejs와 login.ejs의 코드이다.
- join.ejs
<body>
<form action="/join" method="post">
<label for="">아이디</label> <br>
<input type="text" name="user_id"> <br>
<label for="">비밀번호</label> <br>
<input type="text" name="user_pw"> <br>
<button>가입신청</button>
</form>
</body>
- login.jes
<body>
<form action="/login" method="post">
<label for="">아이디</label> <br>
<input type="text" name="user_id"> <br>
<label for="">비밀번호</label> <br>
<input type="text" name="user_pw"> <br>
<button>로그인</button>
<a href="/join">회원가입</a>
</form>
</body>
더보기
- 암호화
- Hash 암호화
- base64
- SHA
- Bcrypt
728x90
'블록체인_9기 > 💚 Node.js' 카테고리의 다른 글
34강_230519_(ORM, Sequelize, ajax, Sequelize모델, 모델간의 관계) (0) | 2023.05.24 |
---|---|
33강_230518_ Node.js(VScode extension으로 Mysql DB연결) (0) | 2023.05.22 |
31강_230515_Node.js(Refresh token, Access token, cookie-parser) (0) | 2023.05.17 |
30강_230512_Node.js(JWT토큰, dotenv, express-session) (0) | 2023.05.16 |
29강(과제X)_230508_Node.js(MVC 패턴으로 게시판 페이지 제작) (1) | 2023.05.09 |