C++에서 Base 64 인코딩 구현
이 튜토리얼에서는 C++에서 base_64
의 인코딩에 대해 설명합니다.
먼저 base_64
인코딩과 이것이 필요한 이유와 위치에 대해 설명합니다. 나중에 C++에서 base_64
인코딩/디코딩에 대해 논의할 것입니다.
인코딩 방식 Base_64
‘Base_64’는 인코딩 체계에 추가된 것입니다. 이진 데이터를 ASCII 문자열로 표현한다는 점에서 이진-텍스트 인코딩과 유사합니다.
차이점은 base_64
인코딩이 radix-64로의 변환을 사용한다는 것입니다. Base_64
인코딩 이름은 base의 수학적 정의에서 따온 것입니다.
밑수는 숫자 체계의 기본 자릿수를 나타냅니다. base_2
와 마찬가지로 기본 숫자는 0과 1입니다.
8진수 체계인 ‘Base_8’은 0부터 7까지 8개의 기본 숫자가 있습니다.
마찬가지로 base_16
에는 0에서 15까지 16개의 기본 숫자가 있으며 여기서 A
에서 F
를 사용하여 10
에서 15
를 나타냅니다. base_64
에는 다음으로 구성된 64개의 기본 숫자가 있습니다.
- 26자 대문자
- 26개의 소문자
- 10자리, 0~9
+
및/
기호 2개
‘Base_64’ 인코딩은 일반적으로 미디어를 통해 데이터를 전송하는 데 사용되며 ASCII를 처리하도록 설계되었습니다. ‘base_64’는 미디어를 통해 전송되는 데이터의 무결성을 유지하려고 합니다.
주요 응용 프로그램은 MIME을 통한 이메일과 XML에 복잡한 데이터를 저장하는 것입니다. Base_64
는 프라이버시 강화 전자 메일(PEM)이라고도 합니다.
Base_64
인코딩 단계
인코딩을 위해 우리는 문자열의 각 문자에 대해 작업을 수행해야 하는 이진 문자열로 데이터를 가지고 있습니다. base_64
에서 인코딩하려면 다음 단계를 수행해야 합니다.
-
각 문자의 ASCII 값을 가져옵니다.
-
ASCII 값의 8비트 이진수를 찾으십시오.
-
숫자를 재배열하여 8비트(2단계에서 얻은)를 6비트로 변환합니다(일부 비트 연산을 포함한 일부 조작 필요(나중에 설명)).
-
6비트 바이너리를 해당 십진수 값으로 변환
-
base_64
(이미 설명한base_64
의 기본 숫자)를 사용하여 각 십진수 값에 해당하는base_64
문자를 할당합니다.
여기서는 8비트 그룹에서 6비트 그룹으로 변환하는 3단계에 대해 자세히 설명합니다.
8비트 그룹을 6비트 그룹으로 변환하는 절차
Base_64
인코딩에서는 처음에 논의한 바와 같이 64
기본 문자/숫자가 있지만 일반적으로 데이터를 바이트 단위로 읽고 씁니다. 1바이트는 8비트로, 0
에서 255
까지 저장할 수 있습니다. 즉, 바이트에서 고유한 256
을 나타낼 수 있습니다.
6비트는 64
고유 값을 나타낼 수 있으며 각 바이트가 Base_64
인코딩 체계의 1자리/문자만 저장하도록 마지막 2비트를 0으로 유지해야 합니다.
각 문자/ASCII 값은 8비트를 사용합니다. 따라서 각 바이트의 2비트를 조정하려면 원본 데이터보다 더 많은 저장 공간이 필요합니다.
Base_64
인코딩의 경우 데이터 손실 없이 6비트로 변환해야 합니다.
8과 6의 LCM을 취하면 24가 됩니다. 3바이트는 24비트이지만 8비트 중 6비트를 사용하면(마지막 2비트는 사용하지 않음) 24비트에 4바이트가 필요합니다. 따라서 데이터 손실 없이 3개의 8비트 그룹을 각각 4개의 6비트 그룹으로 변환할 수 있습니다.
첫 번째 단계는 데이터를 3바이트 세트로 그룹화하는 것입니다. 마지막 그룹의 바이트가 적으면 값이 0인 바이트를 추가하여 그룹을 완성합니다.
다음으로 각 3바이트 세트는 다음 작업을 사용하여 4바이트로 그룹화됩니다. t1, t2 & t3
으로 3바이트 세트를, f1, f2, f3 & f4
로 4바이트 세트를 고려하십시오.
f1 = ( t1 & 0xfc ) >> 2
마스크 0xfc
(바이너리 11111100
에 해당)를 고려하고 집합의 첫 번째 바이트와 마스크 사이에 비트 단위 및 연산을 적용합니다. 다음으로 오른쪽 시프트를 두 번 사용하여 비트별 연산 결과를 구합니다.
시프트 연산은 왼쪽 6비트를 오른쪽으로 옮기고 마지막 2비트는 0이 됩니다.
마스크 0xfc
에는 처음 2비트 0이 있습니다. 작업이 집합의 첫 번째 바이트의 처음 2비트를 0으로 만드는 경우(즉, 첫 번째 바이트의 마지막 6비트가 고려됨), 처음 2비트(이 작업에서 무시됨)는 다음 프로세스에서 고려됩니다.
f2 = ( ( t1 & 0x03 ) << 4 ) + ( ( t2 & 0xf0 ) >> 4 )
여기서 0x03 00000011
마스크는 작업의 첫 번째 바이트에 적용됩니다(즉, 처음 2비트만 고려되고 마지막 6비트는 이전 작업에서 이미 고려됨을 의미합니다). 시프트 연산은 첫 번째 바이트의 결과 2비트를 왼쪽으로 전송하여 표현식의 다섯 번째 및 여섯 번째 비트로 만듭니다.
마스크 0xf0 11110000
은 작업의 두 번째 바이트에 적용됩니다(마지막 4비트만 고려됨을 의미). 시프트 연산은 결과 4비트를 오른쪽으로 전송하여 표현식의 처음 4비트로 만듭니다.
표현식의 첫 번째 부분은 5번째 및 6번째 비트가 켜져 있고, 두 번째 부분은 처음 4비트가 켜져 있고, 집합적으로 처음 6비트가 켜져 있고 마지막 비트가 꺼져 있습니다.
마지막으로 이들을 결합하여 마지막 2비트가 꺼진 바이트를 얻습니다. 이 단계에서 우리는 첫 번째 바이트가 완성되고 두 번째 바이트의 처음 4비트가 고려되는 6비트의 또 다른 바이트를 얻었습니다.
f3 = ( ( t2 & 0x0f ) << 2 ) + ( ( t3 & 0xc0 ) >> 6 )
마스크 0x0f 00001111
은 연산을 위해 두 번째 바이트에 적용됩니다(즉, 처음 4비트만 고려됨). 시프트 연산은 결과 4비트를 왼쪽으로 전송하여 표현식의 세 번째, 네 번째, 다섯 번째 및 여섯 번째 비트로 만들고 처음 2비트를 위한 공간을 만듭니다.
다음으로 마스크 0xc0 11000000
이 연산을 위해 세 번째 바이트에 적용됩니다(즉, 처음 2비트만 고려됨). 시프트 연산은 결과 2비트를 오른쪽으로 전송하여 식의 첫 번째 및 두 번째 비트로 만듭니다.
마지막으로 두 결과가 결합되어 6비트 그룹의 세 번째 바이트를 얻습니다. 다시 세트에서 세트의 두 번째 바이트와 세 번째 바이트의 2비트를 완료했습니다.
f4 = t3 & 0x3f
마지막으로 세 번째 바이트에는 작업만 있으며 마스크 0x3f 00111111
에는 처음 6비트가 켜져 있고 마지막 2비트가 꺼져 있습니다. 세 번째 바이트를 사용한 작업은 세 번째 바이트의 나머지 6비트를 고려합니다.
우리는 이미 base_64
에서 사용되는 64개의 기본 숫자에 대해 논의했습니다. 다음 단계에서 4바이트 집합의 각 바이트(비트 연산을 사용하여 얻음)는 base_64
로 변환되고 문자열로 연결됩니다.
PLAY
라는 단어를 인코딩해 보겠습니다. 첫 번째 단계에서는 각각 3개의 캐릭터가 있는 세트를 만듭니다. 첫 번째 세트에는 PLA
가 있습니다.
다음 단계에는 Y\0\0
이 있습니다. 여기서 \0
은 집합을 완성하기 위해 추가된 null 문자입니다.
각 문자의 ASCII는 80 76 65 89 0 0
입니다. 해당 이진 값은 01010000 01001000 01000001 01011001
입니다.
이제 비트 연산을 해봅시다.
f1 = 01010000 & 11111100 = 01010000 >> 2 = 00010100 = 20
01010000 & 00000011 = 0000000 << 4 = 00000000
식의 첫 부분01001000 & 11110000 = 01010000 >> 4 = 00000101
표현식의 두 번째 부분f2 = 00000000 + 00000101 = 00000101 = 5
, 첫 번째 부분과 두 번째 부분의 결과를 더합니다.01001000 & 00001111 = 00001000 << 2 = 00100000
식의 첫 부분01000001 & 11000000 = 01000000 >> 4 = 00000100
표현식의 두 번째 부분f3 = 00100000 + 00000100 = 00100100 = 36
, 첫 번째 부분과 두 번째 부분의 결과를 더합니다.f4 = 01000001 & 00000011 = 00000001 = 1
이제 두 번째와 세 번째 값이 0인 다음 세트에서 작업을 반복합니다. 따라서 결과는 다음과 같습니다.
f1 = 00010110 = 21
f2 = 00010000 = 16
f3 = 0
f4 = 0
다음으로 이 값을 base_64
로 변환해야 합니다. 또한 디코딩 프로세스가 인식하고 그에 따라 디코딩할 수 있도록 마지막 2바이트에 센티넬/특수 문자를 배치해야 합니다.
첫 번째 세트에는 f1= 20, f2 = 5, f3 = 36 & f4 = 1
이 있습니다. 해당 base_64
값은 UFkB
입니다.
다음 세트는 f1 = 21, f2 = 16, f3 = 0 & f4 = 0
입니다. 다시 해당 base_64
값은 VQ^^
가 됩니다. 여기서 캐럿 기호는 특수 문자로 사용되므로 집합적으로 문자열은 UFkBV^^
입니다.
디코딩 프로세스는 단순히 역 프로세스입니다. 아래의 C++ 코드에서 두 가지 방법을 모두 빠르게 얻을 수 있습니다.
C++에서 Base_64
인코딩 구현
C++에서 인코딩을 수행하는 것은 간단한 프로세스입니다. 우리는 C++에서 신속하게 구현할 수 있습니다(우리가 논의한 단계).
단계적으로 논의하고 마지막으로 2개의 예제와 함께 완전한 코드를 제공할 것입니다.
먼저 base_64
변환을 위해 base_64
의 기본 숫자/문자로 상수 문자열을 정의합니다.
const string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
기능을 코딩하고 디코딩하기 전에 시작 부분에 몇 가지 정의가 있습니다. 주로 인코딩 및 디코딩에 사용되는 일부 마스크가 있습니다.
이러한 마스크 중 6개는 8비트 그룹에서 6비트 그룹으로의 변환을 설명하는 동안 이미 논의되었습니다.
이러한 마스크 중 일부는 디코딩 프로세스에서 6비트 그룹에서 8비트 그룹으로 변환하는 데 사용되며 추가로 2개의 마스크가 더 필요합니다. 총 8개의 마스크가 있습니다.
typedef unsigned char UC;
typedef unsigned int UI;
#define EXTRA '^'
#define MASK1 0xfc
#define MASK2 0x03
#define MASK3 0xf0
#define MASK4 0x0f
#define MASK5 0xc0
#define MASK6 0x3f
#define MASK7 0x30
#define MASK8 0x3c
다음으로 인코딩 기능을 고려하십시오. 우리는 3개의 캐릭터 세트를 만들 것입니다.
다음으로, 이미 자세히 논의한 비트 연산을 사용하여 4자 그룹으로 변환합니다. 마지막으로 4자 그룹의 각 바이트를 변환하고 연결하여 인코딩된 문자열을 만듭니다.
코드는 다음과 같습니다.
string encode_base64(UC const* buf, UI bufLen) {
string encoded = "";
UI i = 0, j = 0, k = 0;
UC temp_a_3[3], temp_4[4];
for (i = 0; i < bufLen; i += 3) {
for (j = i, k = 0; j < bufLen && j < i + 3; j++) temp_a_3[k++] = *(buf++);
for (; k < 3; k++) temp_a_3[k] = '\0';
temp_4[0] = (temp_a_3[0] & MASK1) >> 2;
temp_4[1] = ((temp_a_3[0] & MASK2) << 4) + ((temp_a_3[1] & MASK3) >> 4);
temp_4[2] = ((temp_a_3[1] & MASK4) << 2) + ((temp_a_3[2] & MASK5) >> 6);
temp_4[3] = temp_a_3[2] & MASK6;
for (j = i, k = 0; j < bufLen + 1 && j < i + 4; j++, k++)
encoded += base64_chars[temp_4[k]];
for (; k < 4; k++) encoded += EXTRA; // sentinal value
}
return encoded;
}
이 함수는 2개의 매개변수를 사용합니다. 첫 번째는 원시 데이터(코딩을 위해 전송됨)이고 두 번째는 메시지의 길이입니다. 크기가 3과 4인 2개의 배열을 선언했습니다. 루프 내부에서 크기가 3인 첫 번째 배열에 데이터를 저장합니다.
다음으로 마지막 세트의 바이트가 적은 경우 null 문자를 추가하여 마지막 세트를 완성합니다. 다음으로 8비트 데이터를 6비트 단위 연산으로 변환하는 4개의 명령문이 있습니다.
마지막으로 두 번째에서 마지막 루프까지 4개의 6비트 문자 그룹을 base_64
로 변환합니다.
마지막 루프는 추가 문자를 저장하여 4바이트 세트를 완성합니다. 다음으로 디코딩 기능이 있습니다.
vector<UC> decode_base64(string const& encoded) {
UI i = 0, j = 0, k = 0, in_len = encoded.size();
UC temp_a_3[3], temp_4[4];
vector<UC> decoded;
for (i = 0; i < in_len; i += 4) {
for (j = i, k = 0; j < i + 4 && encoded[j] != EXTRA; j++)
temp_4[k++] = base64_chars.find(encoded[j]);
for (; k < 4; k++) temp_4[k++] = '\0';
temp_a_3[0] = (temp_4[0] << 2) + ((temp_4[1] & MASK7) >> 4);
temp_a_3[1] = ((temp_4[1] & MASK4) << 4) + ((temp_4[2] & MASK8) >> 2);
temp_a_3[2] = ((temp_4[2] & MASK2) << 6) + temp_4[3];
for (j = i, k = 0; k < 3 && encoded[j + 1] != EXTRA; j++, k++)
decoded.push_back(temp_a_3[k]);
}
return decoded;
}
이 함수는 인코딩된 메시지를 가져와 다음 단계를 포함하는 반대 작업을 수행합니다.
base_64
문자 집합에서 얻은 각 문자의 인덱스를 가져와서 4바이트 집합을 만듭니다.- 다시 인코딩 프로세스에서 저장한 특수 문자에 대해 0을 추가합니다.
- 다음으로 반전 비트 연산을 통해 4바이트 세트를 3바이트 세트로 변환합니다(여기서는 이러한 연산에 대해 자세히 다루지 않습니다).
- 마지막으로 3바이트 세트를 결합하여 결합된 디코딩된 메시지를 얻습니다.
마지막으로 여기에 코딩 및 인코딩의 2가지 예가 포함된 완전한 코드가 있습니다.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
typedef unsigned char UC;
typedef unsigned int UI;
#define EXTRA '^'
#define MASK1 0xfc
#define MASK2 0x03
#define MASK3 0xf0
#define MASK4 0x0f
#define MASK5 0xc0
#define MASK6 0x3f
#define MASK7 0x30
#define MASK8 0x3c
const string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string encode_base64(UC const* buf, UI bufLen) {
string encoded = "";
UI i = 0, j = 0, k = 0;
UC temp_a_3[3], temp_4[4];
for (i = 0; i < bufLen; i += 3) {
for (j = i, k = 0; j < bufLen && j < i + 3; j++) temp_a_3[k++] = *(buf++);
for (; k < 3; k++) temp_a_3[k] = '\0';
temp_4[0] = (temp_a_3[0] & MASK1) >> 2;
temp_4[1] = ((temp_a_3[0] & MASK2) << 4) + ((temp_a_3[1] & MASK3) >> 4);
temp_4[2] = ((temp_a_3[1] & MASK4) << 2) + ((temp_a_3[2] & MASK5) >> 6);
temp_4[3] = temp_a_3[2] & MASK6;
for (j = i, k = 0; j < bufLen + 1 && j < i + 4; j++, k++)
encoded += base64_chars[temp_4[k]];
for (; k < 4; k++) encoded += EXTRA; // sentinal value
}
return encoded;
}
vector<UC> decode_base64(string const& encoded) {
UI i = 0, j = 0, k = 0, in_len = encoded.size();
UC temp_a_3[3], temp_4[4];
vector<UC> decoded;
for (i = 0; i < in_len; i += 4) {
for (j = i, k = 0; j < i + 4 && encoded[j] != EXTRA; j++)
temp_4[k++] = base64_chars.find(encoded[j]);
for (; k < 4; k++) temp_4[k++] = '\0';
temp_a_3[0] = (temp_4[0] << 2) + ((temp_4[1] & MASK7) >> 4);
temp_a_3[1] = ((temp_4[1] & MASK4) << 4) + ((temp_4[2] & MASK8) >> 2);
temp_a_3[2] = ((temp_4[2] & MASK2) << 6) + temp_4[3];
for (j = i, k = 0; k < 3 && encoded[j + 1] != EXTRA; j++, k++)
decoded.push_back(temp_a_3[k]);
}
return decoded;
}
int main() {
vector<UC> myData = {'6', '7', '8', '9'};
string encoded = encode_base64(&myData[0], myData.size());
cout << "Encoded String: " << encoded << '\n';
vector<UC> decoded = decode_base64(encoded);
cout << "Decoded Data: ";
for (int i = 0; i < decoded.size(); i++) cout << (char)decoded[i] << ' ';
cout << '\n';
myData = {4, 16, 64};
encoded = encode_base64(&myData[0], myData.size());
cout << "Encoded String: " << encoded << '\n';
decoded = decode_base64(encoded);
cout << "Decoded Data: ";
for (int i = 0; i < decoded.size(); i++) cout << (int)decoded[i] << ' ';
cout << '\n';
return 0;
}
기본적으로 2개의 데이터 세트가 있습니다. 첫 번째 세트에는 숫자 문자가 있습니다. 다음 세트에는 숫자 값이 있습니다. 따라서 마지막 루프에서 디코딩된 메시지를 정수 유형 캐스팅으로 인쇄합니다.
출력:
Encoded String: Njc4OQ^^
Decoded Data: 6 7 8 9
Encoded String: BBBA
Decoded Data: 4 16 64
첫 번째 세트에는 4자(4바이트)가 있고 인코딩된 메시지 Njc4OQ^^
는 6자(마지막 2자는 추가 문자임)가 있습니다. 두 번째 세트에는 3바이트가 있고 인코딩된 메시지 BBBA
에는 4바이트가 있습니다.
base_64
인코딩에서 모든 문자는 1로 설정된 최대 6비트를 가지며, 여기서 해당 64 base_64
기본 문자가 있습니다. 마찬가지로 인코딩된 메시지는 ASCII보다 33% 더 많은 저장 공간이 필요합니다.
추가 저장 공간에도 불구하고 장점은 특수 문자를 관리하는 것입니다. 따라서 이 인코딩 체계는 데이터를 전송하고 무결성을 그대로 유지합니다.