서론
진행 중인 프로젝트에서 간단한 익명 로그인을 구현하게 되었다.
게시글 작성 시 아이디와 패스워드를 입력받은 뒤 해당 게시물의 수정이나 삭제 요청에는 아이디 패스워드를 확인하는 구조이다.
처음에는 firebase의 실시간 데이터베이스를 사용해 게시물의 정보와 아이디, 패스워드 값을 그대로 저장했다.
하지만 유저가 입력한 정보를 데이터베이스에 전송하는 과정에서 아이디와 패스워드가 그대로 드러나기 때문에 절대 이렇게 구현을 마쳐서는 안 된다. 그저 패킷을 열어보기만 해도 유저의 정보가 유출되기 때문에...
아무리 간단한 익명 로그인이어도 '챙길 건 챙기자! '라는 마인드로 패스워드의 암호화를 진행하게 되었다.
암호화
crypto-js
패키지를 사용했고 그중 PBKDF2
알고리즘을 선택했다.
PBKDF2는 salt
라는 것을 사용해서 패스워드를 공격자가 유추해내기 더 어렵게 만든다.
그저 패스워드에 해시를 적용해서 사용하는 것보다는 PBKDF2를 사용하는 것이 보안강도가 높다는 것이다.
해시만 적용해서 사용하면 레인보우 테이블, 사전 공격, 브루트 포스 등 뭐 아무튼 공격자들이 게시글의 패스워드를 비교적 쉽게 찾아낼 수 있게 된다.
구현
import CryptoJS from 'crypto-js';
const pwdEncrypt = (pwd, originSalt, originIv) => {
const salt = originSalt || CryptoJS.lib.WordArray.random(128 / 8).toString(CryptoJS.enc.Hex);
const iv = originIv || CryptoJS.lib.WordArray.random(128 / 8).toString(CryptoJS.enc.Hex);
const iterations = 10000;
const key512Bits10000Iterations = CryptoJS.PBKDF2('1234', CryptoJS.enc.Hex.parse(salt), {
keySize: 512 / 32,
iterations,
});
const encrypted = CryptoJS.AES.encrypt(
pwd,
key512Bits10000Iterations,
{ iv: CryptoJS.enc.Hex.parse(iv) },
);
return {
encryptedPwd: encrypted.toString(),
salt,
iv,
};
};
export default pwdEncrypt;
처음 게시글을 작성할 때에는 랜덤 한 salt와 initial vector을 생성해서 작동하도록 했다.
CryptoJS.PBKDF2('1234', CryptoJS.enc.Hex.parse(salt) ... )
이 부분에서 1234는 Secret PassPhrase
인데 실제로 사용할 때에는 이렇게 쉽고 예측 가능한 값을 사용하면 안 된다.
아무튼 iteration을 반복하며 Secret PassPhrase에 salt를 가미하여 새로운 키를 생성해낸다.
이 키를 통해 AES
암호화 알고리즘으로 encrypt 하면 최종 암호화된 패스워드가 완성된다.
{
"encryptedPwd": "0h+AJgRySA7pAy3abppETQ==",
"iv": "444617aa9550d924a13597b7b5be928c",
"salt": "1cc9864bdcbd34b9d9d9fd5db33ba1dd"
}
pwdEncrypt
함수가 반환하는 객체 값은 위와 같이 데이터베이스에 password 객체로 할당되었다.
iv와 salt를 데이터베이스에 같이 할당해준 이유는 유저가 올바르게 입력한 패스워드에 대하여 동일한 iv와 salt를 사용해서 암호화해야 데이터베이스에 저장되어있는 것과 동일한 암호문이 나오기 때문이다.
이렇게 구현해본 간단한 익명 로그인의 동작 방식은 이렇다.
- 처음 게시글을 작성할 때 데이터베이스에 pwdEncrpyt 함수를 통해 암호화한 패스워드를 저장한다.
- 해당 게시글의 수정/삭제를 시도한다면 아이디와 패스워드를 요구한다.
- 해당 게시글의 데이터베이스에서 iv와 salt를 가져와 입력된 패스워드에 대해 1번과 같은 방식으로 암호화한다.
- 3의 결괏값인 encryptedPwd와 데이터베이스에 저장된 encryptedPwd가 같은지 비교한다.
const checkIdPwValid = () => {
const { iv, salt, encryptedPwd: originPwd } = savedPw;
const inputPwd = pwdEncrypt(password, salt, iv);
if (savedAuthor === author && originPwd === inputPwd.encryptedPwd) {
// Do Something !
}
};
마무리
crypto-js docs를 찾아보며 코드를 작성했는데 사실 독스의 설명이 부실한 감이 없잖아 있다.
따로 찾아보면서 pwdEncrypt 함수를 작성했고 실제로 잘 동작했다.
혹 작성한 내용 중 틀린 부분이 있다면 얼마든지 지적해주세요!
감사합니다,
'유연해지기 > React.js' 카테고리의 다른 글
리액트 절대경로 설정 및 모듈/경로 별명 짓기 with CRA (absolute path, path alias) (0) | 2021.10.21 |
---|---|
[React.js] Error Boundary와 Fallback UI 에 대하여 (0) | 2021.10.08 |
[React.js] useEffect, cleanUp, deps, unMount에 대하여 (0) | 2021.09.29 |
[React.js] useRef와 useState의 용도와 차이 (0) | 2021.09.07 |
[React.js] 고차 컴포넌트 HoC 에 대하여 (0) | 2020.09.13 |