-
[TIL] 스파르타) 아이템 시뮬레이터 2 과제 진행 - 3일 차 (bcrypt)TIL-sparta 2024. 5. 24. 20:26
> 지난번 아이템 시뮬레이터 과제에서는 캐릭터의 코드만 알면 누구나 캐릭터를 삭제할 수 있도록 구현했었는데, 이번 과제에서는 로그인된 사용자 계정에게 속한 캐릭터만 삭제가 가능하고, 캐릭터의 생성 또한 로그인 상태에서만 진행하여 생성된 캐릭터가 계정에 귀속되도록 바꾸는 요구 사항이 추가됐다. 그에 따라 DB에 저장될 계정 비밀번호를 보호하기 위한 bcrypt, 인증 토큰의 생성을 담당할 jsonwebtoken 라이브러리 들을 추가하여 사용해보면서 bcrypt 라이브러리의 사용법 및 hash와 encryption의 차이에 대해 알게된 내용을 정리해보았다.
학습 키워드: Node.js, express, jwt, jsonwebtoken, bcrypt, hash, encryption
bcrypt, hash, and encryption
1) What is it?:
bcrypt는 문자열의 hashing을 담당하는 해싱 함수다. Blowfish 라는 algorithm을 기반으로 제작되었다고 하는데, rainbow table 이라는 해시 복구용 해킹 기법 및 brute-force 탐색을 피하기 위하여 salt 라고 불리는 무작위 문자열을 추가하는 방식을 사용한다.
2) How does it work?:
# bash yarn add bcrypt
import bcrypt from "bcrypt"; const password = "somepassword1234"; const saltRounds = 10; const salt = await bcrypt.genSalt(saltRounds); const hash = await bcrypt.hash(password, salt); // OR const hashWithRounds = await bcrypt.hash(password, saltRounds);
bcrypt JS 라이브러리를 이용한 구현 방법의 예시인데, hashSync()라는 함수도 있지만 공식 npm 페이지에서는 기본적으로 비동기 방식인 hash()를 사용하는 것을 권장하고 있다. 두 가지 방법으로 구현이 가능하지만, 서버의 재부팅 등을 고려할 때 salt 값이 동일하게 유지되어야 한다는 점 때문에 genSalt()로 미리 salt 값을 생성하고, console.log로 출력한 뒤 해당 값을 복사하여 dotenv의 .env 파일에 저장해두고 사용하는 것이 일반적이다. hash()를 사용하여 얻은 hash가 최종적으로 DB에 저장될 비밀번호의 해시 값이다.
// user.route.js // sign-in feature ... // check password if (!(await bcrypt.compare(password, user.password))) { msg = "Password didn't match."; return res.status(401).json({ message: msg }); } // sign a JWT token const token = jwt.sign( { userId: user.userId, username: username }, process.env.SECRET, { expiresIn: 1800 }, ); // put token string into the response header res.setHeader("authorization", "Bearer " + token); ...
위는 현재 프로젝트에 작성해둔 users router 내 sign-in 스크립트의 일부다. 로그인 시도에서 넘어오는 평문 비밀번호를 같은 salt를 사용하여 hashing 한 뒤 DB에 저장한 값과 대조하여 로그인 성공 여부를 결정하면 되는데, 실제론 hashing을 다시 할 필요는 없고 bcrypt에서 제공하는 compare()라는 함수를 이용하여 파라미터로 평문 비밀번호와 DB에 저장된 해시를 순서대로 넘겨주면 쉽게 비교할 수 있다. 과제에서는 로그인이 성공하면 JWT 토큰을 생성하여 response의 header를 통해 전달해주는 것 까지가 요구 사항이다.
3) Why use it?:
해싱은 비밀번호같은 민감한 정보가 DB 안에 그대로 노출되는 것을 막기 위해 사용하며, JWT처럼 토큰에 hash 서명을 추가하고 추후 넘어오는 토큰을 같은 비밀 키를 이용하여 대조하여 위조/변조 여부를 (유효성을) 검사하는 방식으로도 사용된다. 좀 더 일반화하여 설명하면, 변환한 문자열을 다시 풀어서 읽을 (복호화할) 필요가 없는 경우에 사용한다.
4) Difference between hashing and encryption:
이 글을 작성하기 전 (바로 어제자 TIL, 수정함) 까지만 해도 암호화(encryption)와 해싱(hashing) 두 단어의 의미를 혼동하고 있었다. 평문을 전혀 다른 문자열로 바꾸는 과정이라는 점에서 유사하지만, 실제론 두 개념이 각각 목표로 하는 바가 다르고, 결과적으로 사용하는 상황도 다르다. 먼저 암호화의 경우는 암호화 알고리즘(encryption algorithm, or cryptographic algorithm)과 암호 키를 사용하여 1:1 mapping (양방향) 구조로 문자열을 치환하고, 추후에 대칭 키 방식이냐 비대칭 키 방식이냐(이전 TIL, 링크에서 하단 'Why use HTTPS' 부분 참고)에 따라 알맞은 키를 사용해 복호화한다. 여기서 복호화가 바로 두 개념의 핵심 차이점 중 하나다.
해싱의 경우 N:1 mapping (단방향) 구조라서 중복이 많이 발생하고, bcrypt 같은 해싱 알고리즘으로 변환된 문자열은 복호화가 사실상 불가능하다는 차이가 있다. 이는 해싱 함수의 작동 방식과 관련이 있는데, 간단하게 설명하자면 데이터를 여러 chunk로 나눈 뒤 각 청크를 iterate 하면서 해싱을 진행하고, 하나가 완료되면 그 해시를 사용해 다른 청크를 해싱하고, 모두 완료하면 각각의 청크를 합치는 과정에서 또 해싱하는 등 알고리즘마다 다르지만 대체로 여러번 반복해서 해싱하기 때문에 역으로 풀어내려면(참고: hash의 원래 값을 얻는 과정은 decrypt나 decode 라는 단어를 사용하지 않고 해시를reverse 한다 라고 표현한다) 고려해야 하는 경우의 수가 기하 급수적으로 늘어나기 때문이라고 한다.
결론적으로, 암호화의 경우 데이터의 보안을 위해 못 알아보도록 변조한 뒤 안전한 곳에서 복호화하여 데이터를 다시 읽을 수 있는 상태로 바꾸는 것이 목적이라면, 해싱은 평문으로 저장하면 안되는 데이터(대표적으로 비밀번호) 등에 대해 다시 복호화 할 필요가 없는 경우에 사용되고, 추후 비교할 다른 데이터에 똑같은 알고리즘 및 salt 값을 적용해 저장된 데이터와 비교하는 것이 주된 사용 목적이다.
좀 더 자세한 내용은 링크의 답변을 참고하자.
--
REFERENCES:
> 과제 spec
> 과제 repo
> aws, "Cryptographic algorithms"
> stackoverflow, "Fundamental difference between Hashing...", 질문 및 답변 참고
> Wikipedia, "Salt"
> Wikipedia, "bcrypt"
> npm, bcrypt
728x90'TIL-sparta' 카테고리의 다른 글
[TIL] IP, ARP, Routing (0) 2024.05.26 [TIL] 프로그래머스 - 피로도 문제 풀이 (Java, DFS) (0) 2024.05.25 [TIL] 아이템 시뮬레이터 2 과제 시작 - 1일 차 (관계형 DB로 전환하기) (0) 2024.05.23 [TIL] 스파르타) Node.js 숙련주차 강의 수강 - 3일 차 (JWT) (0) 2024.05.22 [TIL] 스파르타) Node.js 숙련주차 강의 수강 - 2일 차 (Cookie, Session) (0) 2024.05.21