-
Base64 에 대해서개발 2024. 6. 27. 00:10
최근 base64를 사용해야하는 상황이 있었다.
생각보다 자주 보이는 형식인데 비해, 어떤 원리로 작동하는지, 어떤 특성을 갖는지 정확하게는 잘 몰랐어서 생각보다 헤매게 되었다.
이참에 base64에 대해 까먹지 않도록 정리할 겸 base64의 원리, 특성, 다른 타입에 대해 작성해보려한다.
base64에 대해
base64는 인코딩 방식이다. 더 정확하게는 이진 데이터를 64진수의 문자열로 변환하는 방식이라고 보면 된다.
64진수라니 너무 많지않은가?
하지만 base64에서 사용하는 문자를 보면 생각보다 많아보이지않을것이다.
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
알파벳 대문자, 알파벳 소문자, 숫자 0-9, 그리고 특수문자 +, / 로 64개의 문자가 모이게된다.
그렇다면 문자열을 어떤 방식으로 64개의 문자열로 치환할 수 있을까? 그 방법에 대해 알아보자.
바이너리 비트 쪼개기
기본 문자열을 이루고 있는 바이너리 데이터는 1바이트. 즉 8비트를 사용한다.
base64를 사용하기 위해서는 64개의 문자열로 치환하기 위해 기존 바이너리 데이터의 8비트를 6비트로 쪼갠다.
2^6 = 64. 즉 64개의 문자열로 나타내기 위해 256개로 나타내는 데이터를 64개로 쪼개는것이다.
6비트 문자열 매칭
6비트로 쪼개진 비트는 64개의 문자열 순서에 맞게 매칭된다.
예를 들어 000000 은 순서 0. 맨처음 'A' 와 매칭된다.
이렇게 'test' 라는 문자열은 dGVzdA로 매칭된다.
패딩
base64는 문자열의 개수가 4의 배수여야한다. 이는 데이터를 6비트로 나누는 과정에서 비트가 부족해지면서 생기는 문제를 막기 위함이다.
이로 인해 'test' 라는 문자열은 최종적으로 base64 'dGVzdA==' 으로 인코딩 되었다.
JS로 직접 구현해보기
javascript에서는 base64의 인코딩, 디코딩을 지원하는 내장함수가 존재한다. 하지만 위에 적어놓은 방식대로 한번 직접 구현해보는것도 좋겠다 싶어서 한번 작성해보았다.
const str = 'test'; const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; function encodeBase64(str) { let bits = ''; for (const char of str) { bits += char.charCodeAt(0).toString(2).padStart(8, 0); } let enc = ''; for (let i = 0; i < bits.length; i += 6) { const byte6 = parseInt(bits.substring(i, i + 6).padEnd(6, 0), 2); enc += base64Chars.charAt(byte6); } enc = enc.padEnd(enc.length + (enc.length % 4), '='); return enc; } encodeBase64(str); // dGVzdA==
const str = 'test'; const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; function getBase64Index(char) { return base64Chars.indexOf(char); } function decodeBase64(base64) { let bits = ''; const safeDecode = base64; // 패딩 제거 for (let char of safeDecode.replace(/=+$/, '')) { const bin = base64Index[char].toString(2); bits += bin.padStart(6, '0'); // 변환과정에서 앞부분 0을 채워줌 } const bytes = []; for (let i = 0; i < bits.length; i += 8) { const byte = bits.substring(i, i + 8); // 8비트로 나뉘어진다는 보장이 있기 때문에, // 마지막 byte가 8자리가 안된다면 // 패딩으로 생긴 바이트이다. if (byte.length === 8) { bytes.push(parseInt(byte, 2)); } } return String.fromCharCode(...bytes); } console.log(decodeBase64(str)); // test
내장함수만큼 효율적이진 않겠지만.. 직접 만들어보는것이 이해가 더 잘될 것 이다..!
base64 url safe
base64 문자열 중 가끔가다 이상한 형식을 볼때가 있다.
base64의 문자열 64개 중에는 대쉬 ('-') 가 존재하지않는다. 그럼 저건 뭘까??
base64의 변형인 url safe 방식이다.
문자열중 +, /, 심지어 패딩으로 사용하는 = 까지, url에 사용되고 있는 기능문자이기 때문에 사용에 영향을 주지않게 변형된 방식이다.
변형 방식은 위의 나열된 기능문자를 치환하여 사용하는 방식이다.
플러스 기호는 마이너스로 ( + => - )
슬래시 기호는 언더스코어로 ( / => _ )
이퀄 기호는 생략하는 방식으로 변형한다.
재밌는 점은 이러한 base64 url safe를 js에서는 지원하지 않는다는 점이다.
이는 base64 url safe가 변형체라서 특수한 상황에서만 사용되는 url safe 방식까지 지원하기에는 내장함수의 용도에서 벗어나기 때문이다.
url safe의 특성이 위 세가지 뿐이기에, 기존에 작성한 코드에서 이러한 특성을 적용해주기만 하면 된다.
const text = 'eyBLZXl3b3JkOiAidGVzdGluZ05vdz8hIiwgaW5kZXg6IDIzLCB0eXBlOiAiYXNkZiIgfSAvLz8_PyE'; const str = '{ Keyword: "testingNow?!", index: 23, type: "asdf" } //???!'; // const base64Chars = // 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // CHECK: base64Chars 변경 const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; function encodeBase64(str) { let bits = ''; for (const char of str) { bits += char.charCodeAt(0).toString(2).padStart(8, 0); } let enc = ''; for (let i = 0; i < bits.length; i += 6) { const byte6 = parseInt(bits.substring(i, i + 6).padEnd(6, 0), 2); enc += base64Chars.charAt(byte6); } // CHECK: 패딩 필요 없음 //enc = enc.padEnd(enc.length + (enc.length % 4), '='); return enc; } function decodeBase64(base64) { let bits = ''; // CHECK: url safe 키워드 변환 const safeDecode = base64.replace('-', '+').replace('_', '/'); for (let char of safeDecode.replace(/=+$/, '')) { // 패딩 제거 const bin = getBase64Index(char).toString(2); bits += bin.padStart(6, '0'); // 변환과정에서 앞부분 0을 채워줌 } const bytes = []; for (let i = 0; i < bits.length; i += 8) { const byte = bits.substring(i, i + 8); if (byte.length === 8) { bytes.push(parseInt(byte, 2)); } } return String.fromCharCode(...bytes); } function getBase64Index(char) { return base64Chars.indexOf(char); } console.log(encodeBase64(str)); // eyBLZXl3b3JkOiAidGVzdGluZ05vdz8hIiwgaW5kZXg6IDIzLCB0eXBlOiAiYXNkZiIgfSAvLz8_PyE console.log(decodeBase64(text)); // { Keyword: "testingNow?!", index: 23, type: "asdf" } //??!
마치며
이 문서에서는 base64 에 대해, 그리고 구현하는 방법에 대해서 알아보았다. 사실 base64 자체를 직접적으로 사용할 일이 많지는 않다고 생각한다. 그러나 가끔가다 써야할일이 생길때, 그리고 내가 겪은 것 처럼 오류가 떴을때 원인을 알 수 있다면 큰 도움이 될 것이다.
'개발' 카테고리의 다른 글
도커 이미지 최적화 및 개선기 (0) 2024.01.26 NextAuth의 로그인 과정을 확인해보자 (SNS 로그인) (2) 2023.12.06 var, let, 그리고 const (1) 2021.11.15