迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > C++ >

C++ 中的 Base 64 编码实现

作者:迹忆客 最近更新:2023/08/25 浏览次数:

本文将讨论 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 个基本数字,包括:

  1. 26个大写字母
  2. 26个小字母
  3. 10 位数字,0 到 9
  4. 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。

现在我们来进行位运算。

  1. f1 = 01010000 & 11111100 = 01010000 >> 2 = 00010100 = 20
  2. 01010000 & 00000011 = 0000000 << 4 = 00000000 表达式的第一部分
  3. 01001000 & 11110000 = 01010000 >> 4 = 00000101 表达式的第二部分
  4. f2 = 00000000 + 00000101 = 00000101 = 5,将第一部分和第二部分的结果相加
  5. 01001000 & 00001111 = 00001000 << 2 = 00100000 表达式的第一部分
  6. 01000001 & 11000000 = 01000000 >> 4 = 00000100 表达式的第二部分
  7. f3 = 00100000 + 00000100 = 00100100 = 36,将第一部分和第二部分的结果相加
  8. 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;
}

该函数获取编码消息并执行相反的操作,其中包括以下步骤。

  1. 获取从base_64字符集中获得的每个字符的索引,并组成4个字节的集合。
  2. 再次,对我们在编码过程中存储的特殊字符添加 0。
  3. 接下来,通过逆位操作将一组 4 字节转换为一组 3 字节(这里不详细介绍这些操作)。
  4. 最后,组合一组 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++ 中的序列化库

下一篇:没有了

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

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++ 中形式参数的重新定义问题。首先,我们将讨论函数定义和形式参数。 接下来,我们将讨论形式参数的重新定义问题。

C++ 工厂方法

发布时间:2023/08/24 浏览次数:78 分类:C++

工厂方法是 C++ 中的一种对象创建设计模式,用于创建对象同时隐藏其组合,以便用户可以使用接口而无需看到任何方法调用。C++ 中的工厂方法 工厂是虚拟构造函数,允许程序员设计让用户与

C++ 调用父方法

发布时间:2023/08/24 浏览次数:132 分类:C++

本文将简要讨论如何在 C++ 中从派生类函数调用父类函数。在C++中调用父类函数 在C++中调用函数就是将控制权转移给函数。

C++ 中的逆矩阵

发布时间:2023/08/24 浏览次数:96 分类:C++

本文将解释矩阵求逆及其使用 C++ 的实现。 为了方便理解C++的实现,我们首先需要理解矩阵逆的概念。矩阵的逆 求矩阵的逆矩阵需要三个步骤。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便