JS 프론트앤드 암호화, 복호화

강정우·2023년 5월 10일
2

JavaScript

목록 보기
38/53
post-thumbnail

프론트 앤드 개발자의 사용자 정보 암호화

  • 우선 FE Developer가 back-end에서 넘어온 사용자의 민감 정보들을 안전하게 저장하기위해 고려할 수 있는 방법들에 대해 소개하겠다.

1. 상태 메모리

  • 프론트엔드 애플리케이션의 메모리에 데이터를 저장할 수 있다.

  • 그러나 이 방법은 브라우저가 닫히면 데이터가 소멸되므로 장기적인 보관이 필요한 민감한 정보에는 적합하지 않다.

2. 쿠키

  • 쿠키는 브라우저에 저장되므로 보안 상의 이슈가 있을 수 있다.

  • 한 정보를 저장할 때는 쿠키를 암호화하거나 토큰 기반의 인증 시스템과 함께 사용하여 보안을 강화해야 한다.

3. 웹 스토리지

  • HTML5에서 도입된 웹 스토리지인 localStorage 또는 sessionStorage를 사용하여 데이터를 저장할 수 있다.

  • 하지만 이러한 스토리지는 클라이언트 측에서 접근 가능하므로 보안에 주의해야 한다.

프론트 앤드 개발자의 사용자 정보 암호화에 사용할 수 있는 방법

localStorage에 저장할 때 암호화를 적용하는 방법은 다양합니다. 다음은 localStorage에 저장할 때 추천하는 암호화 방법 중 일부입니다:

1. CryptoJS

  • CryptoJS는 JavaScript에서 암호화를 수행하기 위한 라이브러리이다.

  • AES, DES, Triple DES, Rabbit, RC4 등 다양한 암호화 알고리즘을 지원한다.

  • CryptoJS 객체를 사용하여 데이터를 암호화하고 복호화할 수 있다.

2. Web Crypto API

  • Web Crypto API는 웹 브라우저에서 암호화 기능을 제공하는 웹 표준이다.

  • 이 API를 사용하여 브라우저에서 데이터를 암호화하고 복호화할 수 있다.
    하지만 모든 브라우저에서 동일한 지원을 보장하지 않으므로 브라우저 호환성에 주의해야 한다.

3. 직접구현

  • 웹 브라우저에서 제공하는 암호화 기능을 사용하지 않고, 직접 암호화 알고리즘을 구현할 수도 있다.

  • JavaScript에서 암호화를 위한 함수와 알고리즘을 작성하여 데이터를 암호화하고 복호화할 수 있다.

  • 물론 이 경우에는 보안 전문가와의 협력해야한다.

주의사항

  1. 키 관리: 암호화에 사용되는 키를 안전하게 보관해야 한다. 키를 분실하거나 유출되면 데이터 보호가 위협받을 수 있다.

  2. 성능: 암호화는 추가적인 계산을 필요로 하므로 성능에 영향을 줄 수 있다. 암호화 알고리즘을 선택할 때 성능 측면 또한 고려해야 한다.

  3. 보안 강화: 데이터를 암호화할 때만으로는 부족하다.
    사용자 데이터를 보호하기 위해 암호화 외에도 접근 제어, 입력 검증, 보안 헤더 설정 등 다른 보안 메커니즘을 함께 사용해야 한다.

CryptoJS VS Web Crypto API

  • 일단 각각의 장단점과 전체적인 특징에 대해 알아봤다.
    하지만 나는 front-end 개발자이고 아직 보안에 깊은 지식은 갖추고있지 않다. 그래서 API를 사용하려한다.
    이때 2보완 방법에 대해 장단점을 각각 알아보자.

CryptoJS의 장점

  1. 브라우저 호환성: CryptoJS는 대부분의 브라우저에서 동작하며 호환성이 좋다.
  2. 사용 편의성: CryptoJS는 쉽게 사용할 수 있는 API와 다양한 암호화 알고리즘을 제공한다.
  3. 구현 시간: CryptoJS는 구현이 비교적 간단하고 빠르게 사용할 수 있다.

Web Crypto API의 장점

  1. 웹 표준: Web Crypto API는 웹 표준으로 제공되므로 브라우저에서 내장된 암호화 기능을 사용할 수 있다.
  2. 성능: Web Crypto API는 브라우저의 네이티브 암호화 기능을 활용하기 때문에 일반적으로 성능이 더 우수하다.
  3. 보안: Web Crypto API는 웹 브라우저에서 제공되는 표준화된 암호화 기능을 사용하므로 보안 측면에서 신뢰성이 높다.

결론

따라서, 브라우저 호환성이 중요한 경우에는 CryptoJS를 사용하는 것이 좋을 수 있다.
그러나 최신 브라우저를 타겟으로 하고 웹 표준을 따르는 것이 중요한 경우에는 Web Crypto API를 사용하는 것이 더 적합하다.

Web Crypto API를 이용한 boiler code

암호화

// 데이터를 암호화하는 함수.
const encryptData = async (data) => {
  const encoder = new TextEncoder();
  const dataBuffer = encoder.encode(data);

  // 웹 크립토 API를 사용하여 데이터를 암호화.
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt']
  );
  const encryptedBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
    key,
    dataBuffer
  );

  // 암호화된 데이터를 Base64로 인코딩하여 반환.
  return btoa(String.fromCharCode.apply(null, new Uint8Array(encryptedBuffer)));
};
  • 위의 코드는 axios를 사용하여 서버에 로그인 요청을 보내고 응답으로 받은 사용자 정보를 sessionStorage에 암호화하여 저장하는 예시이다.
    데이터를 암호화하기 위해 Web Crypto APIAES-GCM 암호화 알고리즘을 사용하였다.

  • 물론 위 코드는 단순한 예시일 뿐이며, 실제로 사용할 때에는 암호화 키 관리, 인증 처리, 예외 처리 등을 고려해야 한다.
    또한, 복호화 시에는 저장된 데이터를 복호화하여 사용해야 한다.

복호화

// sessionStorage에서 암호화된 사용자 데이터를 가져와 디코드하여 복호화하는 함수.
const decryptUserData = async (data) => {
  // 변수로 sessionStorage에서 암호화된 데이터를 넘겨줌.
  const encryptedData = data;

  if (!encryptedData) {
    // 암호화된 데이터가 없으면 처리할 로직을 추가해야함.
    return null;
  }

  // Base64로 인코딩된 암호화된 데이터를 디코드.
  const encryptedBuffer = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));

  // 웹 크립토 API를 사용하여 데이터를 복호화.
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['decrypt']
  );
  const decryptedBuffer = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
    key,
    encryptedBuffer
  );

  // 디코드된 데이터를 문자열로 변환하여 반환.
  const decoder = new TextDecoder();
  const decryptedData = decoder.decode(decryptedBuffer);

  // JSON 형태로 파싱하여 사용자 데이터 객체로 반환.
  return JSON.parse(decryptedData);
};
  • 위의 코드는 매개변수로 sessionStorage에서 암호화된 데이터를 넘겨주면 디코드 및 복호화하여 사용자 데이터를 보여주는 예시이다.
    이전에 사용한 암호화 과정과 동일한 AES-GCM 암호화 알고리즘을 사용하여 복호화를 수행한다.

  • 복호화 과정에서 예외 처리와 오류 처리를 적절하게 추가해야 한다.

  • 암호화된 데이터의 복호화는 암호화된 데이터와 해당 암호화에 사용된 키와 알고리즘 등의 정보가 필요하다.
    이러한 정보를 안전하게 관리하고 보호하는 것 또한 굉장히 중요한 요소이다.

주의사항

  1. 전송 보안: 암호화된 데이터를 전송하는 경우, 데이터의 무결성과 기밀성을 보장하기 위해 HTTPS와 같은 보안 프로토콜을 사용하는 것이 좋다.

  2. 보안 강화: 암호화된 데이터를 저장할 때 추가적인 보안 강화를 고려할 수 있다.
    예를 들어, 사용자의 추가 인증 요소를 사용하거나 암호화된 데이터를 분리하여 저장하는 등의 방법을 고려할 수 있다.

  3. 암호화 키의 주기적인 갱신: 암호화 키는 일정한 주기로 변경하여 보안을 강화하는 것이 좋다.
    키를 주기적으로 갱신하고 이전 키를 폐기하는 정책을 수립하는 것이 좋다.

Crypto-js를 이용한 boiler code

암호화

import CryptoJS from 'crypto-js';

const plaintext = 'Hello, World!'; // 암호화할 텍스트

const key = CryptoJS.enc.Hex.parse('0123456789abcdef0123456789abcdef'); // 256비트 암호화 키
const iv = CryptoJS.enc.Hex.parse('abcdef9876543210abcdef9876543210'); // 초기화 벡터


// 텍스트 암호화
const encrypted = CryptoJS.AES.encrypt(plaintext, key, { iv: iv });

// 암호화된 결과를 Base64로 인코딩
const encryptedBase64 = encrypted.toString();

console.log(encryptedBase64); // 암호화된 결과 출력
  • 위 코드에서는 crypto-js에서 객체를 import하여 작성하였다.
    CryptoJS.AES.encrypt 함수를 사용하여 텍스트를 암호화하고, 암호화된 결과를 Base64로 인코딩하여 출력하도록 설정하였다.

복호화

import CryptoJS from 'crypto-js';

const decodeFn = (data) => {
  const encryptedBase64 = data; // 암호화된 Base64 데이터
  
  const key = CryptoJS.enc.Hex.parse('0123456789abcdef0123456789abcdef'); // 256비트 암호화 키
  const iv = CryptoJS.enc.Hex.parse('abcdef9876543210abcdef9876543210'); // 초기화 벡터
  
  // Base64로 인코딩된 암호화된 데이터를 디코드
  const ciphertext = CryptoJS.enc.Base64.parse(encryptedBase64);
  
  // 텍스트 복호화
  const decrypted = CryptoJS.AES.decrypt(
    { ciphertext: ciphertext },
    key,
    { iv: iv }
  );
  // 복호화된 결과를 문자열로 변환
  const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
  return decryptedText
}
  • 참고로 복호화 과정에서는 암호화에 사용된 키(key)와 초기화 벡터(iv)를 지정해야한다.

  • CryptoJS.enc.Base64.parse() 함수를 사용하여 Base64로 인코딩된 암호화된 데이터를 디코드한 뒤, CryptoJS.AES.decrypt() 함수를 사용하여 텍스트를 복호화한다.
    최종적으로 toString() 함수를 사용하여 복호화된 결과를 문자열로 변환하고 출력한다.

  • 암호화에서 키(key)와 초기화 벡터(iv)는 보안성을 위해 매우매우매우 중요한 역할을 한다.
    키는 암호화 및 복호화에 사용되는 비밀 값으로, 안전하고 예측할 수 없는 값을 가져야 한다.
    초기화 벡터는 블록 암호화 모드에서 사용되며, 각 암호화 작업마다 고유한 값을 가져야 한다.

  • 따라서, 키와 초기화 벡터는 보안에 매우 중요하며, 무작위성과 예측 불가능성이 요구된다.
    즉, 랜덤한 값을 사용하거나 안전한 난수 생성기를 통해 생성하는 것이 좋다.

// 256비트 암호화 키 생성
const key = CryptoJS.lib.WordArray.random(32);

// 초기화 벡터 생성
const iv = CryptoJS.lib.WordArray.random(16);

+ Web Crypto API를 이용한 boiler code(feat.Web Storage)

암호화

async function encryptData(data) {
  const encoder = new TextEncoder();
  const dataBuffer = encoder.encode(data);

  // 웹 크립토 API를 사용하여 데이터를 암호화.
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt']
  );
  const initializationVector = crypto.getRandomValues(new Uint8Array(12));
  const encryptedBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: initializationVector },
    key,
    dataBuffer
  );

  // 암호화된 데이터를 Base64로 인코딩하여 반환.
  const encryptedData = btoa(String.fromCharCode.apply(null, new Uint8Array(encryptedBuffer)));

  // 암호화된 데이터와 초기화 벡터를 sessionStorage에 저장.
  sessionStorage.setItem('encryptedData', encryptedData);
  sessionStorage.setItem('initializationVector', JSON.stringify([...initializationVector]));

  return encryptedData;
}

복호화

async function decryptData(data) {
  // 변수로 sessionStorage에서 암호화된 데이터와 초기화 벡터를 가져옴.
  const encryptedData = data;
  const initializationVector = JSON.parse(sessionStorage.getItem('initializationVector'));

  if (!encryptedData || !initializationVector) {
    // 암호화된 데이터가 없으면 처리할 로직을 추가해야함.
    return null;
  }

  // Base64로 인코딩된 암호화된 데이터를 디코드.
  const encryptedBuffer = new Uint8Array(atob(encryptedData).split('').map(c => c.charCodeAt(0)));

  // 웹 크립토 API를 사용하여 데이터를 복호화.
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['decrypt']
  );
  const decryptedBuffer = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: new Uint8Array(initializationVector) },
    key,
    encryptedBuffer
  );

  // 디코드된 데이터를 문자열로 변환하여 반환.
  const decoder = new TextDecoder();
  const decryptedData = decoder.decode(decryptedBuffer);

  // JSON 형태로 파싱하여 사용자 데이터 객체로 반환.
  return JSON.parse(decryptedData);
}
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글