Base64 (베이스64) 인코딩 완벽 정복: 당신이 알아야 할 모든 것

Base64 (베이스64) 인코딩 완벽 정복: 당신이 알아야 할 모든 것

Base64 (베이스64) 심층 이해: 원리, 응용 그리고 프론트엔드 실전 활용법

프론트엔드 개발에서 프로젝트 최적화는 성능 향상을 위한 아주 중요한 부분인데요.

흔한 최적화 전략 중 하나는 페이지의 HTTP (에이치티티피) 요청 수를 줄이기 위해 내장된 작은 이미지들을 Base64 (베이스64) 문자열로 적절히 대체하는 것입니다.

이때, 보통 특정 킬로바이트(KB) 크기를 넘지 않는 작은 이미지여야 한다고 강조하는데요.

그렇다면 Base64 (베이스64)란 정확히 무엇일까요?

그리고 왜 프론트엔드 최적화에 도움이 될 수 있을까요?

함께 자세히 알아보겠습니다.

첫 만남

아마 이런 문자열이 낯설지 않을 수도 있는데요.

data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......

이런 고정된 형식을 통해 우리는 이미지를 표현할 수 있고, 브라우저는 이를 인식하여 해당 이미지를 완벽하게 보여줄 수 있습니다.

위 예시는 SVG (에스브이지) 형식의 이미지를 보여주는데요.

사실 브라우저가 지원하는 어떤 형식이든 이미지를 불러올 수 있습니다.

이 문자열은 Base64 (베이스64) 인코딩을 기반으로 생성되며, base64, 뒤에 오는 긴 문자열이 바로 Base64 (베이스64)로 인코딩된 문자열입니다.

Base64 (베이스64)는 어떻게 탄생했을까요?

인터넷 발전 초기에 이메일은 가장 효과적인 응용 프로그램 중 하나였습니다.

하지만 초기에는 이메일용 SMTP (에스엠티피) 전송 프로토콜이 7비트 ASCII (아스키) 코드만 전송할 수 있었습니다.

ASCII (아스키) 코드는 영어를 기반으로 설계되었기 때문에, 비영어권 국가의 텍스트와 같은 자원들은 이 프로토콜을 통해 보낼 수 없었습니다.

이 문제를 해결하기 위해 나중에 MIME (마임, Multipurpose Internet Mail Extensions)이 등장했습니다.

MIME (마임)은 이메일의 주요 구조를 추가하고 비 ASCII (아스키) 코드에 대한 인코딩 및 전송 규칙을 정의했는데, 이것이 바로 Base64 (베이스64)의 기원입니다.

문자 인코딩 지식을 깊이 이해하고 싶다면, 프론트엔드 개발에서 이해해야 할 문자 인코딩 지식을 참고할 수 있습니다.

기본 정의

Base64 (베이스64)는 64개의 출력 가능한 문자를 기반으로 이진 데이터를 표현하는 인코딩 및 디코딩 방법입니다.

인코딩 및 디코딩 특성 때문에 주요 기능은 보안이 아니라, 다양한 게이트웨이 간에 콘텐츠를 오류 없이 전송할 수 있도록 보장하는 것입니다.

이 64개의 출력 가능한 문자에는 대문자 A-Z 26개, 소문자 a-z 26개, 숫자 0-9 10개로 총 62개의 문자가 포함되며, 여기에 다른 두 문자 +와 /가 추가됩니다.

Base64 (베이스64)는 인덱스 기반 인코딩이며, 각 문자는 인덱스에 해당합니다.

구체적인 관계도는 다음과 같습니다.

[이미지 설명: Base64 인코딩 테이블. 0부터 63까지의 인덱스에 해당하는 A-Z, a-z, 0-9, +, / 문자들이 나열되어 있습니다.]

이것이 바로 이름에 "64"가 들어간 이유이기도 합니다.

인코딩 방법

64는 2의 6제곱과 같으므로, Base64 (베이스64) 문자 하나는 실제로 6개의 이진 비트(bit)를 나타냅니다.

하지만 이진 데이터에서 1바이트는 8비트(bit)에 해당합니다.

따라서 3바이트(3 x 8 = 24비트)의 문자열이나 이진 데이터는 정확히 4개의 Base64 (베이스64) 문자(4 x 6 = 24비트)로 변환될 수 있습니다.

왜 3바이트 단위로 묶을까요?

6과 8의 최소공배수는 24이고, 24비트는 정확히 3바이트이기 때문입니다.

구체적인 인코딩 방법은 다음과 같습니다.

3바이트마다 그룹으로 묶습니다.

세 바이트는 총 24개의 이진 비트를 가집니다.


이 24개의 이진 비트를 4개의 그룹으로 나누고, 각 그룹은 6개의 이진 비트를 갖습니다.


각 6비트 그룹 앞에 두 개의 00을 추가하여 32비트, 즉 4바이트로 확장합니다.

(이 부분은 설명이 약간 혼동될 수 있는데, 실제로는 6비트씩 묶어서 바로 Base64 문자로 매핑합니다.

32비트로 확장하는 과정은 일반적인 설명은 아닙니다.)


각 바이트는 64보다 작은 숫자에 해당하며, 이것이 문자 번호입니다.

(정확히는 6비트 덩어리가 0~63 사이의 숫자를 나타내고, 이 숫자가 Base64 문자의 인덱스가 됩니다.)


그런 다음 문자 인덱스 관계 테이블에 따라 각 문자 번호는 문자에 해당하며, Base64 (베이스64)로 인코딩된 문자를 얻습니다.

[이미지 설명: "two"라는 문자열을 Base64로 인코딩하는 과정. 't', 'w', 'o' 각 문자의 ASCII 값을 이진수로 변환하고, 이를 6비트씩 묶어 Base64 테이블에서 해당 문자를 찾아 "dHdv"를 얻는 과정을 보여줍니다.]

예를 들어, 위 그림의 문자열 "two"는 변환되어 해당 인코딩을 얻습니다.

용량 증가

3개의 문자가 Base64 (베이스64) 변환을 통해 인코딩되면 최종적으로 4개의 문자가 됩니다.

이는 각 6비트 위치에 2개의 0이 채워져 8비트 위치가 되고, 이는 1바이트에 해당하기 때문입니다.

(이 설명도 약간의 오해가 있을 수 있습니다.

정확히는 3바이트(24비트)가 4개의 Base64 문자(각 6비트, 총 24비트)로 표현되므로, 문자 수로는 3개가 4개로 늘어나는 것입니다.

비트 수는 동일합니다.

다만, Base64 문자는 ASCII 문자이므로 각 문자가 1바이트를 차지하여 결과적으로 데이터 크기가 약 1/3 증가합니다.)

여기서 정확히 3분의 1이 더 많아집니다.

따라서 일반적인 상황에서 Base64 (베이스64)로 인코딩된 데이터의 볼륨은 일반적으로 원본 데이터보다 3분의 1 더 큽니다.

이것이 앞서 언급한 Base64 (베이스64) 인코딩을 사용하여 이미지를 최적화할 때 작은 아이콘이어야 한다고 강조하는 이유입니다.

모든 이미지를 이런 식으로 처리하면 정적 파일이 크게 증가하여 적절하지 않습니다.

"=" 기호

세 개의 영어 문자는 정확히 4개의 Base64 (베이스64) 문자로 변환될 수 있습니다.

만약 문자 길이가 3의 배수가 아니면 어떨까요?

어떤 규칙을 따라야 할까요?

실제 Base (베이스) 인코딩 사용 시, 우리는 종종 65번째 문자인 = 기호의 존재를 발견합니다.

이 등호는 이 특별한 상황에 대한 처리 방법입니다.

3바이트 미만의 부분에 대해서는 실제로 24개의 이진 비트가 될 때까지 끝에 0이 추가됩니다.

하지만 바이트 수를 계산할 때 총 길이를 직접 3으로 나눈다는 점에 유의해야 합니다.

나머지가 1이면 끝에 = 하나를 직접 추가합니다.

나머지가 2이면 == 두 개를 추가합니다.

따라서 트랜스코딩된 문자열에 추가해야 하는 접미사 등호는 1개 또는 2개입니다.

구체적인 상황은 다음 그림에서 볼 수 있습니다.

[이미지 설명: 문자열 길이가 3의 배수가 아닐 때 Base64 인코딩 시 패딩('=')이 추가되는 예시. 1글자("d")는 "ZA=="로, 2글자("do")는 "ZG8="로 인코딩되는 과정을 보여줍니다.]

그림의 두 번째 경우, 단일 문자 d는 인덱스 문자 테이블의 인덱스 0과 구별하는 데 사용됩니다.

이때 얻은 인코딩에는 인덱스 0에 해당하는 A 문자가 있고, == 두 개가 직접 추가됩니다.

(이 설명에서 'd'와 인덱스 0을 구별한다는 부분은 명확하지 않습니다.

단순히 입력 데이터가 1바이트일 때 패딩이 두 개 붙는다는 의미로 이해하는 것이 좋습니다.)

비 ASCII (아스키) 문자

Base64 (베이스64)는 ASCII (아스키) 문자만 인코딩할 수 있으므로, 일본어 문자와 같은 비 ASCII (아스키) 문자의 경우 먼저 일본어 문자를 ASCII (아스키) 문자로 변환한 다음 인코딩해야 합니다.

(정확히는, 비 ASCII 문자를 UTF-8 (유티에프-팔)과 같은 바이트 시퀀스로 변환한 후, 이 바이트 시퀀스를 Base64로 인코딩합니다.)

인코딩 및 디코딩 방법

btoa와 atob

JavaScript (자바스크립트)는 Base64 (베이스64) 인코딩을 처리하기 위한 두 가지 기본 메서드인 btoa() (비투에이)와 atob() (에이투비)를 제공합니다.

btoa() (비투에이): 문자열 또는 이진 값을 Base64 (베이스64)로 인코딩된 문자열로 변환합니다.

btoa (비투에이) 메서드는 ASCII (아스키) 코드 문자만 직접 처리할 수 있다는 점에 유의해야 합니다.

비 ASCII (아스키) 코드 문자의 경우 오류를 보고합니다.


atob() (에이투비): Base64 (베이스64)로 인코딩된 문자열을 디코딩합니다.

atob (에이투비) 메서드에 전달된 문자열 매개변수가 유효한 Base64 (베이스64) 인코딩이 아니거나(예: 비 ASCII (아스키) 문자) 길이가 4의 배수가 아니면 오류를 보고합니다.

btoa('you') // 'eW91'가 반환됩니다.
atob('eW91') // 'you'가 반환됩니다.
btoa('馬鹿') // Uncaught DOMException: 인코딩할 문자열에 Latin1 범위를 벗어나는 문자가 포함되어 있습니다. 라는 에러 발생
atob('y') // Uncaught DOMException: 디코딩할 문자열이 올바르게 인코딩되지 않았습니다. 라는 에러 발생



일본어 문자 처리

btoa (비투에이)와 atob (에이투비)는 ASCII (아스키) 문자, 즉 단일 바이트 문자만 인코딩을 지원하기 때문에, 우리가 일반적으로 접하는 일본어 문자는 2-4바이트 문자입니다.

따라서 일본어 문자는 먼저 UTF-8 (유티에프-팔) 인코딩으로 변환한 다음, UTF-8 (유티에프-팔) 인코딩을 하나의 문자로 간주하여 여러 단일 바이트 문자를 인코딩할 수 있습니다.

일본어의 경우, encodeURIComponent() (인코드유알아이컴포넌트)와 decodeURIComponent() (디코드유알아이컴포넌트) 두 가지 메서드를 사용할 수 있습니다.

encodeURIComponent() (인코드유알아이컴포넌트): 비 ACSII (아스키) 문자를 UTF-8 (유티에프-팔)로 인코딩합니다.


decodeURIComponent() (디코드유알아이컴포넌트): 디코딩에 사용됩니다.


다음은 일본어를 인코딩하고 디코딩하는 방법입니다.

window.btoa(encodeURIComponent('馬鹿'))
// 'JUU5JUE2JUFDJUU5JUI5JUJG'가 반환됩니다.
decodeURIComponent(window.atob('JUU5JUE2JUFDJUU5JUI5JUJG'))
// '馬鹿'가 반환됩니다.



타사 라이브러리

예를 들어, js-base64 (제이에스 베이스64)가 있습니다.

프론트엔드 개발에서는 이러한 타사 라이브러리를 사용하여 Base64 (베이스64) 관련 작업을 보다 편리하게 처리할 수도 있습니다.

일반적인 프론트엔드 애플리케이션

다음으로 프론트엔드 개발에서 Base64 (베이스64) 인코딩의 몇 가지 일반적인 사용 시나리오를 살펴보겠습니다.

프론트엔드에서 Base64 (베이스64)의 대부분 응용 프로그램은 이미지 처리를 위한 것이며, 일반적으로 DataURL (데이터유알엘) 방법을 기반으로 합니다.

Data URL (데이터 유알엘)은 data: 접두사, MIME (마임) 유형(데이터 유형 표시), base64 플래그(텍스트인 경우 선택 사항), 데이터 자체의 네 부분으로 구성됩니다.

구체적인 형식은 data:[<mime type>][;base64],<data>입니다.

여기서 네 번째 부분 <data>, 즉 데이터 자체가 Base64 (베이스64) 문자열입니다.

작은 이미지 트랜스코딩

즉, 처음에 언급된 이미지 최적화를 위해 Base64 (베이스64)를 사용하여 요청 수를 줄이는 시나리오입니다.

img (이미지) 태그 아래 또는 css (씨에스에스)에서 사용할 수 있습니다.

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......Ii8+PC9nPjwvc3ZnPg==">



.icon {
  background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0c......Ii8+PC9nPjwvc3ZnPg==);
}



Vue (뷰) 또는 React (리액트) 프레임워크를 사용할 때 url-loader (유알엘 로더)를 통해 Base64 (베이스64)로 변환할 아이콘 크기를 설정할 수도 있습니다.

.loader('url-loader') // url-loader를 사용합니다.
.tap(options => {
  options.limit = 10240 // 10kb로 제한합니다.
  return options
})



파일 읽기

웹 환경에서는 파일 데이터를 읽기 위한 FileReader (파일리더) API (에이피아이)가 제공됩니다.

readAsDataURL() (리드애즈데이터유알엘) 메서드를 사용하여 파일 데이터를 Base64 (베이스64)로 인코딩된 문자열 데이터로 읽을 수 있습니다.

let reader = new FileReader() // FileReader 객체를 생성합니다.
reader.onload = () => { // 파일 읽기가 완료되면 실행됩니다.
  let base64Img = reader.result // 읽은 결과를 base64Img 변수에 저장합니다.
};
reader.readAsDataURL(file) // 파일을 Data URL 형식으로 읽습니다.



이 방법은 이미지 업로드에 일반적으로 사용됩니다.

Canvas (캔버스) 이미지 생성

Canvas (캔버스)는 본질적으로 비트맵 이미지입니다.

캔버스를 이미지로 내보내는 toDataURL() (투데이터유알엘) 메서드를 제공하며, 이 이미지는 Base64 (베이스64)로 인코딩된 형식으로 저장됩니다.

const dataUrl = canvasEl.toDataURL() // 캔버스 내용을 Data URL로 변환합니다.
// data:image/png;base64,PHN2ZyB4bWxucz0iaHR0c......와 같은 형식이 됩니다.



기타

이미지 표시 처리 외에도 Base64 (베이스64)로 인코딩된 문자열은 특수 데이터 전송, 간단한 인코딩 및 암호화, 코드 난독화 및 일부 인증서에서도 볼 수 있습니다.

요약

마지막으로 Base64 (베이스64)의 특징을 요약해 보겠습니다.

이진 데이터를 문자열(ASCII (아스키) 코드)로 변환하여 데이터 전송을 용이하게 합니다.


브라우저는 Base64 (베이스64)로 인코딩된 이미지를 직접 표시하여 요청을 줄일 수 있습니다.


인코딩된 데이터는 최소 3분의 1 더 커지며, 인코딩 및 디코딩을 처리하기 위한 추가적인 방법이 필요합니다.


Base64 (베이스64)를 깊이 이해함으로써 프론트엔드 개발에서 보다 합리적으로 사용하여 성능을 최적화하는 동시에 다양한 데이터 전송 및 표시 요구 사항을 더 잘 처리할 수 있습니다.