사이드 프로젝트나 어떤 프로젝트를 할 때, 사용자 정보를 입력 받아 백엔드 서버로 전송하는 부분은 항상 있었던 것 같은데 그 대표적인 예가 회원가입, 로그인일 것 같다. 이뿐만이 아니라 사용자 정보를 입력받아 처리가 필요한 부분이 있을 수 있는데 그 때, 평문으로 보내지는 사용자 정보를 암호화하여 백엔드로 전송해보려고 한다.
사용자 정보를 암호화하지 않고 평문으로 그대로 보낼 경우, 아래와 같이 개발자 도구의 네트워크 탭에서 해당 정보를 모두 확인할 수 있다. 해당 요청의 Payload 탭을 클릭하면 사용자 정보를 볼 수 있다.
클라이언트단에서 사용자 정보를 암호화해서 보낼 경우, 백엔드쪽에서도 이를 복호화하는 과정이 필요하기에 논의는 필요하다. 내가 클라이언트단에서 사용할 라이브러리는 CryptoJS라는 라이브러리인데 이를 사용해 AES256 방식으로 데이터를 암호화하고 복호화해보려고한다.
1. AES256 방식이란?
AES방식은 양방향 암호화방식의 대표적인 예로 암호문을 복호화할 수 있도록 구현된 암호 알고리즘이라고 할 수 있다. 단방향 암호화 방식은 복호화가 불가능한 반면, AES와 같은 양방향 암호화방식은 복호화가 가능하다. 그래서 복호화가 가능한 방식을 선택했고, 그 중에서도 대칭키 암호 알고리즘을 선택했다. AES 알고리즘은 다양한 키 길이를 사용할 수 있도록 제공되는데 128, 192, 256 비트 중에서 선택하면 된다. 그 중, AES256 방식은 key 길이 32바이트를 충족하면 된다.
[대칭키 암호 알고리즘]
- 하나의 키로 암호화/복호화를 모두 수행하는 알고리즘
- 암호화하는 암호키와 복호화하는 해독키가 같음
- 따라서 이 키는 절대 외부에 노출되면 안되고 해당 키를 Secret Key라고 부름
- 프론트엔드와 백엔드 간의 동일한 키를 공유해야하기 때문에 키 관리에 대한 어려움이 있고, 잦은 키 변경이 있는 경우에는 불편함을 초래
[비대칭키 암호 알고리즘]
- 암호화/복호화 시의 키가 서로 다른 키를 의미함
- 공개키와 개인키 사용
- 한 쌍의 키가 존재하며 하나는 특정 사람만이 가지는 개인키이며 하나는 누구나 가질 수 있는 공개키
- 비대칭키를 사용한 암호화 방식에는 공개키로 암호화하는 경우와 개인키로 암호화하는 경우로 구분할 수 있음
2. 사용자 정보를 암호화하기 위한 Key 생성
나는 AES256 방식을 선택했기 때문에 Key의 길이는 32바이트가 되어야한다. 그래서 CryptoJs를 이용해 32 바이트의 랜덤 secretKey를 생성해주었다. 대칭키 알고리즘을 사용하기 때문에 암호화/복호화에서 모두 같은 시크릿키를 사용해야한다. 실제로 서비스에서 사용할 때는 해당 키가 노출되지 않게 잘 숨겨야한다.
const secretKey = CryptoJS.lib.WordArray.random(32).toString()
시크릿 키를 콘솔에 찍어보면 아래와 같이 나오는데 길이는 32바이트, 즉 길이는 64이다.
837ecb8ce0a55b7747f6e615a9a69c6c3a6073f48bb4cc5748d1bb44c5b3cd70
3. 사용자 정보 암호화/복호화
시크릿키를 발급받았으니 이제 본격적으로 사용자 정보를 암호화하는 방법을 알아보려고 한다.
위와 같이 이름, 전화번호, 생년월일에 대한 정보가 담긴 객체가 있고 각각의 버튼을 클릭했을 때, 해당 역할을 수행하도록 만들어보았다. 우선 가장 먼저 암호화하는 방법을 살펴보면 아래와 같이 코드를 작성해줄 수 있다.
// 암호화
let hash = "";
const userInfo = {
name: '김하나',
phone: '01022221111',
birthday: '19900312',
};
document.getElementById('encrypt').addEventListener('click', function (e) {
hash = CryptoJS.AES.encrypt(JSON.stringify(userInfo), secretKey).toString();
console.log('암호화', hash); // U2FsdGVkX19EKI+ItgHwUHm+Brjj8Hirk/f0+ew21Z53KgefJck5BriRGX/ZsfDaWkAf4f7rV0fmBzmOlvh81b4eVYxEcUJc8b+Q7sz6K+dCvp3qvUSMIilgHY3o5ddn
});
암호화 버튼을 클릭하면 해당 클릭 이벤트가 실행되는데 암호화하고자하는 객체와 시크릿키를 이용해 암호화해준다. 그 결과를 콘솔에 찍어보면 위와같이 출력된다. 다음으로는 해당 결과를 복호화시켜봤다.
// 복호화
document.getElementById('decrypt').addEventListener('click', function (e) {
const bytes = CryptoJS.AES.decrypt(hash, secretKey)
const decrypted = JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
console.log('복호화', decrypted) // {name: '김하나', phone: '01022221111', birthday: '19900312'}
})
복호화한 결과를 콘솔에 찍어보면 기존 데이터의 형태가 그대로 출력된다. 최종 결과는 아래처럼 출력되는데, 암호화한 결과를 백엔드 서버로 보내주면 백엔드쪽에서 복호화하는 과정을 거칠 것이다. 그 전에 복호화가 제대로 되는지까지 테스트해봤다. 크게 어렵지는 않았던 것 같다.
4. 리액트에서의 사용
리액트에서 사용하는 것도 크게 다르지 않다. 리액트에서는 npm이나 yarn으로 패키지를 설치해서 사용하면 되는데 아래 순서대로 따라가면 된다.
1) crypto-js 설치
npm i crypto-js
2) 설치 후, import
import CryptoJS from 'crypto-js';
3) 예시 코드
import React, { useState } from 'react';
import CryptoJS from 'crypto-js';
const Crypto = () => {
const [secretKey, setSecretKey] = useState('');
const [hash, setHash] = useState('');
const userInfo = {
name: '김하나',
phone: '01022221111',
birthday: '19900312',
};
const getSecretKey = () => {
const token = CryptoJS.lib.WordArray.random(32).toString();
setSecretKey(token);
console.log('시크릿키', token);
};
const encrypted = () => {
// AES알고리즘 사용 암호화
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(userInfo), secretKey).toString();
setHash(encrypted);
console.log('암호화', encrypted);
};
const decrypted = () => {
// AES알고리즘 사용 복호화
const bytes = CryptoJS.AES.decrypt(hash, secretKey);
const decrypted = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log('복호화', decrypted);
};
return (
<div>
<div>
<div>이름 : {userInfo.name}</div>
<div>전화번호 : {userInfo.phone}</div>
<div>생년월일 : {userInfo.birthday}</div>
</div>
<button onClick={getSecretKey}>키발급</button>
<button onClick={encrypted}>암호화</button>
<button onClick={decrypted}>복호화</button>
</div>
);
};
export default Crypto;
각각의 버튼을 클릭하면 키 발급, 암호화, 복호화 함수가 실행되고, 각각의 값을 콘솔에 찍어보면 아래와 같이 나온다.