C++ 中的 Base 64 编码实现
本文将讨论 C++ 中的 base_64 编码。
首先,我们将讨论 base_64 编码以及需要它的原因和位置。 稍后,我们将讨论 C++ 中的 base_64 编码/解码。
编码方案 Base_64
Base_64 是对编码方案的补充。 它类似于二进制到文本编码,因为它以 ASCII 字符串表示二进制数据。
不同之处在于,base_64 编码使用转换为 radix-64。 Base_64 编码名称来自基数的数学定义。
基数表示数字系统的基本数字。 就像base_2只有2个基本数字,0和1。
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字符。
在这里,我们将讨论步骤 3 的细节,即从 8 位组到 6 位组的转换。
将 8 位组转换为 6 位组的过程
在 Base_64 编码中,正如一开始所讨论的,我们有 64 个主要字符/数字,而通常我们以字节为单位读取/写入数据。 1 个字节有 8 位,其中我们可以存储 0 到 255,这意味着我们可以在一个字节中表示 256 个唯一的值。
6 位可以表示 64 个唯一值,其中我们必须将最后 2 位保留为 0,以便每个字节仅存储 Base_64 编码方案的 1 位数字/字符。
每个字符/ASCII 值占用 8 位。 因此,调整每个字节的2位需要比原始数据更多的存储空间。
对于Base_64编码,我们必须将它们转换为6位而不丢失任何数据。
如果我们取 8 和 6 的 LCM,我们得到 24。3 个字节有 24 位,但是如果我们使用 8 位中的 6 个(最后 2 位不使用),我们需要 4 个字节来表示 24 位。 因此,在没有任何数据丢失的情况下,我们可以将 3 个 8 位组中的每一个转换为 4 个 6 位组。
第一步是将数据分组为 3 个字节的组。 如果最后一组的字节数较少,则通过添加具有 0 值的字节来完成该组。
接下来,使用以下操作将每组 3 个字节分为 4 个字节。 我们将一组 3 个字节视为 t1、t2 和 t3,将 4 个字节视为 f1、f2、f3 和 f4。
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 位。
表达式的第一部分的第五和第六位为开,第二部分的前 4 位为开,总的来说,前 6 位为开,最后一位为关。
最后,我们将它们组合起来得到一个最后 2 位关闭的字节。 在这一步中,我们获得了另一个6位的字节,其中第一个字节已完成,并且考虑第二个字节的前4位。
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 个角色的集合。 在第一盘中,我们有解放军。
在下一阶段,我们有 Y\0\0。 这里,\0 是为了完成该集合而添加的空字符。
每个字符的 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+/";
在讨论编码和解码功能之前,我们首先有一些定义。 主要有一些掩码用于编码和解码。
在解释从 8 位组到 6 位组的转换时已经讨论了其中的 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
Next, consider the encoding function. We will make sets of 3 characters.
Next, we will convert them into groups of 4 characters with bit operations, already discussed in detail. Finally, we will convert each byte of our group of 4 characters and concatenate them to create an encoded string.
Here is the code:
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 的数组。在循环内,我们将数据存储在第一个大小为 3 的数组中。
接下来,如果最后一组中的字节较少,我们添加空字符来完成最后一组。 接下来,我们有 4 个语句将 8 位数据转换为 6 位逐位运算。
最后,在倒数第二个循环中,我们将一组 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 <vector>
#include <string>
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编码中,每个字符最多有6位设置为1,其中我们有相应的64个base_64主要字符。 同样,编码消息比 ASCII 需要多 33% 的存储空间。
尽管有额外的存储空间,但优点是可以管理特殊字符。 因此,这种编码方案传输数据并保持完整性。
相关文章
C++ 中的序列化库
发布时间:2023/08/25 浏览次数:134 分类:C++
-
在本文中,您将了解不同的 C++ 序列化库。首先,我们将了解序列化及其在 C++ 中的用途。 接下来,我们将讨论 C++ 中的序列化库以及如何在我们的程序中使用它们。
C++ 中的 time(NULL) 函数
发布时间:2023/08/24 浏览次数:162 分类:C++
-
本文将讨论 C++ 中的 time(NULL) 函数。C++ 中的 time(NULL) 函数 time() 函数,参数为 NULL,time(NULL),
C++类函数声明中的const关键字
发布时间:2023/08/24 浏览次数:136 分类:C++
-
在C++中,const关键字定义了那些在程序执行期间不会改变并保持不变的值。 对于变量及其保存的数据来说,这听起来非常简单。
C++ 中的 shellExecute() 函数
发布时间:2023/08/24 浏览次数:60 分类:C++
-
这个小型编程教程将讨论 C++ 中的 ShellExecute() 库函数。 该库函数主要用于通过C++程序打开或执行任何文件(例如脚本文件)。C++ 中的 ShellExecute() 函数
C++ 中默认参数的重新定义
发布时间:2023/08/24 浏览次数:170 分类:C++
-
在本文中,您将学习如何处理 C++ 中默认参数错误的重新定义。 C++ 中的默认参数必须在方法或函数的声明或定义中指定,但不能同时指定,因为存在重复。
C++ 形式参数的重新定义
发布时间:2023/08/24 浏览次数:133 分类:C++
-
在本文中,我们将讨论 C++ 中形式参数的重新定义问题。首先,我们将讨论函数定义和形式参数。 接下来,我们将讨论形式参数的重新定义问题。