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바이트로 두 문자를 만들고 (6비트, 2비트 + 4비트 0패딩) 두 개의 '=' 문자를 추가한다. - 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 |