본문 바로가기

강의자료/C/C++

033. BASE64 인코딩/디코딩

◈ 이 글은 강의를 위하여 제가 직접 작성한 내용입니다. 따라서 퍼가실 경우, 출처를 명확히 해주시기 바랍니다!!
◈ This ariticle was written by me for teaching. So if you want to copy this article, please clarify the source!!


BASE64란? 바이너리 데이터를 텍스트로 표현하기 위한 표현법.
기본원리: 바이너리 데이터를 3bytes(24bits)단위로 나눈 후, 6bits씩 쪼개서 4bytes로 변환을 한다.
=> 바이너리 데이터는 1byte가 8bit이기 때문에 이를 문자로 표현하면 특수기호를 포함해서 사람이 알아볼 수 없는 형태가 된다.
하지만 이를 1byte가 6bit인 base64로 바꾸게 되면, 문자로 표현할 경우, 알파벳과 숫자, 그리고 +, / 만으로 모두 표현할 수 있게 된다.
(전체 64개의 문자 + 패드문자 =, 합해서 총 65개의 문자로 표현하게 된다.)
이는 공백과 같은 특수문자를 token으로 사용하여 전송하는 곳에서 데이터를 온전하게 전송하기 위해 사용된다.
주된 사용처 : 이메일에서의 첨부파일전송.
우선 하나의 변수에서 비트별로 데이터를 쪼개서 사용하는 방법을 알아보자.
// 데이터 비트 쪼개기.
#include  
using namespace std;

void main( )
{
	int num;

	// 오른쪽 16비트에 100 저장하기.
	num = (num&0xffff0000) + (100&0xffff);
	// 왼쪽 16비트에 200 저장하기.
	num = (num&0xffff) | ((200&0xffff) << 16);

	// 오른쪽 16비트의 값 꺼내오기.
	int right = num & 0xffff;
	// 왼쪽 16비트의 값 꺼내오기.
	int left = (num >> 16) & 0xffff;

	cout << num << endl;
	cout << right << endl;
	cout << left << endl;

	// origin의 왼쪽 6비트를 dd의 오른쪽 6비트로 복사하시오.
	char origin = 250;					// 11111010
	char copy = (origin>>2)&0x3f;		// 00111110 -> 62
	// origin의 최상위비트가 1이므로 오른쪽 쉬프트를 하면 1이 채워진다.
	// 그러므로 & 0x3f를 하지않으면 원하는 결과가 나오지 않는다.
	cout << (int)copy << endl;

	// copy의 4,5번째 비트의 값을 꺼내서 출력.
	char res = (copy & (3<<3)) >> 3;	// 00111110 & 00011000
	cout << (int)res << endl;
}
위의 내용을 기반으로 BASE64에 대한 코드를 작성한다.
// BASE64란? 바이너리 데이터를 텍스트로 표현하기 위한 표현법.
// 기본원리: 바이너리 데이터를 3bytes(24bits)단위로 나눈 후, 6bits씩 쪼개서 4bytes로 변환을 한다.
//			 바이너리 데이터는 1byte가 8bit이기 때문에 이를 문자로 표현하면 특수기호를 포함해서 
//			 사람이 알아볼 수 없는 형태가 된다. 하지만 이를 1byte가 6bit인 base64로 바꾸게 되면,
//			 문자로 표현할 경우, 알파벳과 숫자, 그리고 +, / 만으로 모두 표현할 수 있게 된다.
//			 (전체 64개의 문자 + 패드문자 =, 합해서 총 65개의 문자로 표현하게 된다.)
// 이는 공백과 같은 특수문자를 token으로 사용하여 전송하는 곳에서 데이터를 온전하게 전송하기 위해 사용된다.
// 주된 사용처 : 이메일에서의 첨부파일전송.

// >> Encode Routine.
// 11001100 10101010 00100101, 11011010 10011001
// 110011/00 1010/1010 00/100101/, 110110/10 1001/1001
// 00110011 00001010 00101000 00100101, 00110110 00101001 00100100 PAD
// 51       10       40       37        54       41       36       =       

// 11001100 10101010 00100101, 11011010
// 110011/00 1010/1010 00/100101/, 110110/10
// 00110011 00001010 00101000 00100101, 00110110 00100000 PAD PAD
// 51       10       40       37        54       32       =   =

#include 
#include 
using namespace std;

#define MAX_CHAR			1024
#define BIT_6				0x3F
#define BIT_4				0x0F
#define BIT_2				0x03
#define PAD					64

const char BASE64TABLE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

long BASE64RTABLE(char ch)
{
	int base64length = strlen(BASE64TABLE);
	for(int i=0; i < base64length; i++)
	{
		if(ch == BASE64TABLE[i]) 
		{
			if(i == PAD) 
				return 0;
			else 
				return i;
		}
	}

	return -1;
}

long Base64EncodeSize(char* str);
long Base64DecodeSize(char* str);
void Base64Encode(char* src, char* dest, int countofdest);
void Base64Decode(char* src, char* dest, int countofdest);

void main()
{
	char origin[MAX_CHAR];
	char* encode = NULL;
	char* decode = NULL;

	cout << "인코딩할 문자열을 입력하십시오." << endl;
	cin.getline(origin, _countof(origin));

	int encode_size = Base64EncodeSize(origin);
	encode = new char[encode_size];

	Base64Encode(origin, encode, encode_size);

	cout << "[인코딩된 문자열]" << endl;
	cout << encode << endl;

	int decode_size = Base64DecodeSize(encode);
	decode = new char[decode_size];

	Base64Decode(encode, decode, decode_size);

	cout << "[디코딩된 문자열]" << endl;
	cout << decode << endl;

	delete [] encode;
	delete [] decode;

	system("PAUSE");
}

long Base64EncodeSize( char* str )
{
	float size = strlen(str)*sizeof(char)/3.0f;
	if(size > (int)size) size = int(size)+1.0f;

	return long(size*4 + 1);	// +1 : null 문자를 위해 추가.
}

long Base64DecodeSize( char* str )
{
	float size = strlen(str)*sizeof(char)/4.0f;
	// base64 텍스트는 4로 나누어 떨어져야 한다.
	assert((size == (int)size) && "invalid base64 encoded text.");

	return ((int)size*3 + 1);	// +1 : null 문자를 위해 추가.
}

void Base64Encode( char* src, char* dest, int countofdest )
{
	int need_size = Base64EncodeSize(src);
	assert(countofdest >= need_size && "dest buffer size is too small.");

	int src_size = strlen(src);
	int count = 0;	// 현재까지 인코딩한 바이트 수.
	int idx = 0;	// dest 배열 인덱스.

	while( src_size - count >= 3 )	// src_size - count : 남은 바이트 수.
	{
		char* start = src+count;	// 인코딩을 시작할 위치.
		
		char i = 0;

		// [0]12345678 -> 00123456
		i = ((start[0] >> 2) & BIT_6);
		dest[idx++] = BASE64TABLE[i];

		// [0]12345678 -> 00000078 -> 00780000 + [1]12345678 -> 00001234 = 00781234
		i = ((start[0] & BIT_2) << 4) + ((start[1] >> 4) & BIT_4);
		dest[idx++] = BASE64TABLE[i];

		// [1]12345678 -> 00005678 -> 00567800 + [2]12345678 -> 00000012 = 00567812
		i = ((start[1] & BIT_4) << 2) + ((start[2] >> 6) & BIT_2);
		dest[idx++] = BASE64TABLE[i];

		// [2]12345678 -> 00345678
		i = (start[2] & BIT_6);
		dest[idx++] = BASE64TABLE[i];

		count += 3;					// 인코딩한 바이트 수 증가.
	}

	// 원본 데이터가 3바이트로 나누어 떨어지지 않을 경우,
	if(src_size - count > 0)
	{
		char* start = src+count;
		char i = 0;

		// 1바이트가 남았거나 2바이트가 남았거나 둘 다 맨 앞의 6비트는 꺼내올 수 있다.
		i = ((start[0] >> 2) & BIT_6);
		dest[idx++] = BASE64TABLE[i];

		// 2바이트가 남은 경우,
		if( src_size - count == 2 )
		{
			i = ((start[0] & BIT_2) << 4) + ((start[1] >> 4) & BIT_4);
			dest[idx++] = BASE64TABLE[i];

			i = ((start[1] & BIT_4) << 2);
			dest[idx++] = BASE64TABLE[i];

			dest[idx++] = BASE64TABLE[PAD];
		}
		// 1바이트가 남은 경우,
		else
		{
			i = ((start[0] & BIT_2) << 4);
			dest[idx++] = BASE64TABLE[i];

			dest[idx++] = BASE64TABLE[PAD];

			dest[idx++] = BASE64TABLE[PAD];
		}
	}

	dest[idx] = '\0';
}

void Base64Decode( char* src, char* dest, int countofdest )
{
	int need_size = Base64DecodeSize(src);
	assert(countofdest >= need_size && "dest buffer size is too small.");

	int src_size = strlen(src);
	int count = 0;	// 현재까지 디코딩한 바이트 
	int idx = 0;	// dest 배열 인덱스.

	while( src_size - count > 0 )
	{
		char* start = src+count;

		int v1 = BASE64RTABLE(start[0]);
		int v2 = BASE64RTABLE(start[1]);
		int v3 = BASE64RTABLE(start[2]);
		int v4 = BASE64RTABLE(start[3]);

		// [v1]00123456 -> 12345600 + [v2]00123456 -> 00000012 = 12345612
		dest[idx++] = (v1 << 2) + (v2 >> 4);

		// [v2]00123456 -> 34560000 + [v3]00123456 -> 00001234 = 34561234
		dest[idx++] = (v2 << 4) + (v3 >> 2);

		// [v3]00123456 -> 56000000 + [v4]00123456 = 56123456
		dest[idx++] = (v3 << 6) + v4;

		count += 4;
	}

	dest[idx] = '\0';
}