개발/개발 노트

Base64 변환 인코딩

로파이 2022. 4. 5. 00:06

Base64 인코딩
임의의 데이터를 문자열 종류에 영향을 받지않는 가장 기본적인 ASCII 코드 형식으로 인코딩 하는 방법이다.

 

Base64 문자열 테이블

A-Z, a-z, 0-9 그리고 규격에 따라 정해진 나머지 두 개의 문자열을 합하여 총 64개의 문자열으로 인코딩한다.

MIME 규격으로는 '+'(63) 와 '/'(64)를 사용한다.

인코딩 방식

  • 64개의 문자열에 맵핑하기 위해서 2의 6제곱, 6비트 정보가 필요하다.
  • 편의를 위해 4개씩 문자열을 만드는데 필요한 24비트, 총 3바이트씩 끊어서 인코딩한다.
  • 위키 피디아의 예시처럼 "Man"을 인코딩하면 "TWFu"가 된다.
Text content M a n
ASCII 77 97 110
Bit pattern 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0
Index 19 22 5 46
Base64-Encoded T W F u

참고: https://ko.wikipedia.org/wiki/%EB%B2%A0%EC%9D%B4%EC%8A%A464

기본적으로 3바이트 씩 끊는데 3의 배수를 만들기 위해 추가하는 바이트는 0으로 패딩한다.
만약 데이터의 사이즈가 3으로 나누어 떨어지지 않을 경우 다음 방법으로 나머지 문자열을 추가한다.

  1. 1바이트가 남는 경우
    1바이트로 두 문자를 만들고 (6비트, 2비트 + 4비트 0패딩) 두 개의 '=' 문자를 추가한다.
  2. 2바이트가 남는 경우
    2바이트로 세 문자를 만들고 (6비트, 6비트, 4비트 + 2비트 0패딩) 한 개의 '=' 문자를 추가한다.

'='를 추가하는 이유는 decoding할 때 원래 데이터 보다 긴 복원 데이터를 만들지 않기 위해서이다.

예시 코드

class Base64
{
private:
	class ConvertRule
	{
	private:
		std::array<char, 64> indexTable;
		std::unordered_map<char, unsigned char> invertTable;

	public:
		ConvertRule()
		{
			int index = 0;
			for (char ch = 'A'; ch <= 'Z'; ++ch)
				indexTable[index++] = ch;
			for (char ch = 'a'; ch <= 'z'; ++ch)
				indexTable[index++] = ch;
			for (char ch = '0'; ch <= '9'; ++ch)
				indexTable[index++] = ch;
			
			// MIME 규격
			indexTable[62] = '+';
			indexTable[63] = '/';

			for (int idx = 0; idx < 64; ++idx)
				invertTable[indexTable[idx]] = idx;
		}

		char Convert(int idx) const { return indexTable[idx]; }
		unsigned char Invert(char ch) { return invertTable[ch]; }
	};
public:
	static std::string Encode(const std::string& data)
	{
		static ConvertRule rule;

		std::string encoded;
		size_t dataSize = data.size();
		size_t chunkSize = dataSize / 3;
		for (size_t i = 0; i < chunkSize; ++i)
		{
			auto byte_0 = data[3 * i];
			auto byte_1 = data[3 * i + 1];
			auto byte_2 = data[3 * i + 2];

			auto code_0 = byte_0 >> 2;
			auto code_1 = ((byte_0 & 0x3) << 4) | (byte_1 >> 4);
			auto code_2 = ((byte_1 & 0xF) << 2) | (byte_2 >> 6);
			auto code_3 = byte_2 & 0x3F;

			encoded.push_back(rule.Convert(code_0));
			encoded.push_back(rule.Convert(code_1));
			encoded.push_back(rule.Convert(code_2));
			encoded.push_back(rule.Convert(code_3));
		}

		size_t remainSize = dataSize % 3;
		if (remainSize == 1)
		{
			auto last_byte = data[dataSize - 1];
			auto last_code_0 = last_byte >> 2;
			auto last_code_1 = ((last_byte & 0x3) << 4);

			encoded.push_back(rule.Convert(last_code_0));
			encoded.push_back(rule.Convert(last_code_1));
			encoded.push_back('=');
			encoded.push_back('=');
		}
		else if (remainSize == 2)
		{
			auto last_byte_0 = data[dataSize - 2];
			auto last_byte_1 = data[dataSize - 1];
			
			auto last_code_0 = last_byte_0 >> 2;
			auto last_code_1 = ((last_byte_0 & 0x3) << 4) | (last_byte_1 >> 4);
			auto last_code_2 = ((last_byte_1 & 0xF) << 2);

			encoded.push_back(rule.Convert(last_code_0));
			encoded.push_back(rule.Convert(last_code_1));
			encoded.push_back(rule.Convert(last_code_2));
			encoded.push_back('=');
		}

		return encoded;
	}
	
	static std::string Decode(const std::string& encoded)
	{
		static ConvertRule rule;

		size_t paddingSize = 0;
		for (auto iter = encoded.rbegin(); iter != encoded.rend() && *iter == '='; ++iter)
		{
			++paddingSize;
		}
		size_t readableSize = encoded.size() - paddingSize;

		std::string decoded;
		size_t readableChunkSize = readableSize / 4;
		for (size_t i = 0; i < readableChunkSize; ++i)
		{
			auto code_0 = rule.Invert(encoded[4 * i]);
			auto code_1 = rule.Invert(encoded[4 * i + 1]);
			auto code_2 = rule.Invert(encoded[4 * i + 2]);
			auto code_3 = rule.Invert(encoded[4 * i + 3]);

			auto byte_0 = (code_0 << 2) | (code_1 >> 4);
			auto byte_1 = ((code_1 & 0xF) << 4) | (code_2 >> 2);
			auto byte_2 = ((code_2 & 0x3) << 6) | code_3;

			decoded.push_back((char)byte_0);
			decoded.push_back((char)byte_1);
			decoded.push_back((char)byte_2);
		}

		if (paddingSize == 1)
		{
			size_t lastIdx = readableSize - 1;
			auto last_code_0 = rule.Invert(encoded[lastIdx - 2]);
			auto last_code_1 = rule.Invert(encoded[lastIdx - 1]);
			auto last_code_2 = rule.Invert(encoded[lastIdx]);

			auto last_byte_0 = (last_code_0 << 2) | (last_code_1 >> 4);
			auto last_byte_1 = ((last_code_1 & 0xF) << 4) | (last_code_2 >> 2);

			decoded.push_back((char)last_byte_0);
			decoded.push_back((char)last_byte_1);
		}
		else if (paddingSize == 2)
		{
			size_t lastIdx = readableSize - 1;
			auto last_code_0 = rule.Invert(encoded[lastIdx - 1]);
			auto last_code_1 = rule.Invert(encoded[lastIdx]);

			auto last_byte = (last_code_0 << 2) | (last_code_1 >> 4);

			decoded.push_back((char)last_byte);
		}

		return decoded;
	}
};

'개발 > 개발 노트' 카테고리의 다른 글

VirtualBox에서 인터넷 사이트 DNS 접속 오류  (0) 2022.05.08
REST API  (0) 2022.04.30
x64 아키텍쳐 레지스터  (0) 2022.04.21
C++ UnitTest 프로젝트 만들기  (0) 2022.01.30