如何用C++对Base64进行编码和解码

611 阅读16分钟

Base64是一个由64个字符组成的字符集,每个字符由6位组成。所有这64个字符都是可打印的字符。一个字符就是一个符号。因此,64进制字符集的每个符号都由6位组成。这样的6位被称为六位数。一个字节或八位数由8位组成。ASCII字符集由127个字符组成,其中一些是不可打印的。所以,ASCII字符集的一些字符不是符号。ASCII字符集的一个符号是由8位组成的。

计算机中的数据是以每个8比特的字节存储的。数据是以每个8位的字节从计算机中发出的。数据以每个8比特的字节形式被接收到计算机中。

一个字节流可以被转换为六进制流(每个符号6比特)。而这就是base64编码。六元组流可以被转换成字节流。这就是base64解码。换句话说,一个ASCII字符流可以被转换为一个六元组符号流。这就是编码,反之就是解码。从八位数(字节)符号流转换而来的六位数符号流比八位数符号流要长。换句话说,base64的字符流比相应的ASCII字符流要长。那么,编码成base64和解码成base64并不像刚才所表达的那样简单明了。

这篇文章解释了用C++计算机语言对Base64进行编码和解码。文章的第一部分正确解释了base64的编码和解码。第二部分展示了如何利用C++的一些特性对base64进行编码和解码。在本文中,"八位数 "和 "字节 "这两个词可以互换使用。

文章内容

提升到Base 64

一个由2个符号组成的字母表或字符集可以用每个符号一个比特来表示。让字母表的符号包括:0和1。在这种情况下,0是位0,1是位1。

一个由4个符号组成的字母表或字符集可以用每个符号的两个比特来表示。让字母表符号由以下部分组成。0, 1, 2, 3.在这种情况下,0是00,1是01,2是10,3是11。

一个由8个符号组成的字母表可以用每个符号3位来表示。让字母表的符号包括。0, 1, 2, 3, 4, 5, 6, 7.在这种情况下,0是000,1是001,2是010,3是011,4是100,5是101,6是110,7是111。

一个由16个符号组成的字母表可以用每个符号的4位来表示。让字母表的符号包括。0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。在这种情况下,0是0000,1是0001,2是0010,3是0011,4是0100,5是0101,6是0110,7是0111,8是1000,9是1001,A是1010,B是1011,C是1100,D是1101,E是1110和F是1111。

一个由32个不同符号组成的字母表可以用每个符号的5位来表示。

这使我们看到一个由64个不同符号组成的字母表。一个由64个不同符号组成的字母表可以用每个符号的6位来表示。有一个由64个不同符号组成的特殊字符集,称为base64。在这个集合中,前26个符号是英语口语中的26个大写字母,按其顺序排列。这26个符号是第一个从0到25的二进制数字,其中每个符号是一个六位数,六个比特。接下来从26到51的二进制数字是英语口语的26个小写字母,按其顺序排列;同样,每个符号,是一个六位数。接下来从52到61的二进制数字是10个阿拉伯数字,按其顺序排列;同样,每个符号是六位数。

62的二进制数字是符号+,63的二进制数字是符号/。 Base64有不同的变体。所以有些变体对62和63的二进制数有不同的符号。

显示索引、二进制数和字符的对应关系的base64表格是。

Base64字母表

索引二进制字符指数二进制图表指数二进制图表指数二进制符号
0000000A16010000Q32100000g48110000w
1000001B17010001R33100001h49110001x
2000010C18010010S34100010i50110010y
3000011D19010011T35100011j51110011z
4000100E20010100U36100100k521101000
5000101F21010101V37100101l531101011
6000110G22010110W38100110m541101102
7000111H23010111X39100111n551101113
8001000I24011000Y40101000o561110004
9001001J25011001Z41101001p571110015
10001010K26011010a42101010q581110106
11001011L27011011b43101011r591110117
12001100M28011100c44101100s601111008
13001101N29011101d45101101t611111019
14001110O30011110e46101110u62111110+
15001111P31011111f47101111v63111111/

填充=

实际上有65个符号。最后一个符号是=,其二进制数仍由6位组成,即11101。它与base64的符号9并不冲突--见下文。

编码Base64
六位一体的位域

考虑一下这个词。

    dog

这个词有三个ASCII字节,分别是:

    01100100 01101111 01100111

加入。这些是3个八位数,但由4个六位数组成,如下所示:

    011001 000110 111101 100111

从上面的base64字母表来看,这4个六元组就是符号。

    ZG9n

注意,"狗 "在base64中的编码是 "ZG9n",这是不可以理解的。

Base64将一个3个八位数(字节)的序列编码为4个六位数的序列。3个八位数或4个六位数是24比特。

现在考虑一下下面这个词。

    it

这个词有两个ASCII的八位数,分别是:

    01101001 01110100

加入。这是两个八位组,但由两个六位组和4位组成。一个base64的字符流是由六位数组成的(每个字符6位)。因此,两个零位必须附加到这16位上,才能有3个六元组,也就是。

    011010 010111 010000

这还不是全部。Base64序列是由每组4个六位数组成的;也就是说,每组24位。填充字符=是11101。两个零位已经被附加到16位上,有18位。因此,如果将填充字符的6个填充位附加到18位上,将有24位的要求。就是说。

    011010 010111 010000 111101

最后六位是填充六位,= 。 这24位由4个六位组成,其中最后一个六位是base64符号的前4位,后面是两个零位。

现在,考虑以下一个字符的字。

  I

这个词有一个ASCII八位数,即:

  01001001

这是一个八位数,但由1个六位数和2个比特组成。一个base64的字符流是由六位数组成的(每个字符6位)。因此,必须在这8个比特上加上4个零比特,才能有2个六元组,也就是。

    010010 010000

这还不是全部。Base64序列是由每组4个六位数组成的;也就是说,每组24位。填充字符=是11101,有6个比特长。四个零位已经被附加到8位上,有12位。这还没有达到四个六位数。因此,必须再加上两个填充六位数,才能组成4个六位数,即:。

    010010 010000 111101 111101

Base64的输出流

在程序中,必须制作一个base64字母数组,其中索引0有8位的字符,A;索引1有8位的字符,B;索引2有8位的字符,C,直到索引63有8位的字符,/。

因此,三个字符的字,"狗 "的输出将是 "ZG9n "的四个字节,用比特表示为

    01011010 01000111 00111001 01101110

其中Z是01011010,共8位;G是01000111,共8位;9是00111001,共8位;n是01101110,共8位。这意味着从原始字符串的三个字节中,输出了四个字节。这四个字节是base64字母阵列的值,其中每个值都是一个字节。

两个字符的单词 "it "的输出将是四个字节的 "aXQ=",用比特表示为

01100001 01011000 01010001 00111101

从数组中获得。这意味着,从两个字节中,仍有四个字节被输出。

一个字符 "I "的输出将是四个字节的 "SQ==",用比特表示为

01010011 01010001 00111101 00111101

这意味着从一个字节开始,仍有四个字节被输出。

61(11101)的六位数被输出为9(00111001)。一个=(11101)的六位数被输出为=(00111101)。

新长度

这里有三种情况需要考虑,以便对新长度进行估计。

  • 字符串的原始长度是3的倍数,例如,3、6、9、12、15等。在这种情况下,新的长度将正好是原始长度的133.33%,因为三个八位数最后变成了四个八位数。
  • 字符串的原始长度是两个字节,或者在3的倍数之后以两个字节结束。在这种情况下,新的长度将高于原始长度的133.33%,因为两个八位字节的字符串部分最终变成了四个八位字节。
  • 在这种情况下,新的长度将超过原长度的133.33%(比前一种情况更多),因为一个八位数的字符串部分最终变成了四个八位数。

行的最大长度

在从原始字符串经过base64字母阵列,最后得到至少133.33%长的八位数后,任何输出字符串的长度都不能超过76个八位数。当一个输出字符串的长度为76个字符时,在添加另一个76个字节或更少的字符之前,必须添加一个换行符。一个长的输出字符串有所有的部分,每个部分由76个字符组成,除了最后一个,如果它没有达到76个字符。程序员使用的行分隔符可能是换行符,'\n';但它应该是"\r\n"。

解码Base64

要进行解码,要做与编码相反的事情。使用以下算法:

  • 如果收到的字符串长于76个字符(octets),将长字符串分割成一个字符串数组,去掉行分隔符,这可能是"\r\n "或'\n'。
  • 如果有多于一行的76个字符,那么就意味着除了最后一行外,所有的行都是由每组四个字符组成的。每组将产生三个使用base64字母阵列的字符。这四个字节在转换为三个八位数之前,必须先转换为六个六位数。
  • 最后一行,或者说是字符串可能有的唯一一行,仍然由四组字符组成。最后一组四个字符的结果可能是一个或两个字符。要知道最后一组四个字符是否会产生一个字符,请检查该组的最后两个八位字节是否都是ASCII,=。 如果该组产生两个字符,那么只有最后一个八位字节应该是ASCII,=。 在这个最后的四元序列前面的任何四元序列的字符都像上一步那样处理。

传输错误

在接收端,除分行符以外的任何字符或不是base64字母阵列值的字符都表示传输错误;并应予以处理。本文不涉及传输错误的处理。注意:在76个字符中出现=这个字节,并不是一个传输错误。

C++的位特征

结构元素的基本成员可以被赋予8以外的位数。 下面的程序说明了这一点。

 #include <iostream>

    using namespace std;


    struct S3 {

        unsigned int a:6;

        unsigned int b:6;

        unsigned int c:6;

        unsigned int d:6;

    }s3;


    int main()

    {

        s3.a = 25;

        s3.b = 6;

        s3.c = 61;

        s3.d = 39;


        cout<<s3.a<<", "<<s3.b<<", "<<s3.c<<", "<<s3.d <<endl;


        return 0;

    }

输出结果是:

25,6,61,39

输出的整数与分配的一样。然而,每个整数在内存中占6位,而不是8位或32位。注意比特数是如何分配的,在声明中,用冒号。

从八位数中提取前6位

C++没有一个函数或操作符可以从一个八位数中提取第一组比特。为了提取前6位,将八位数的内容右移2位。在左端空出的两个位被填上零。所得到的八位数应该是一个无符号字符,现在是一个整数,由八位数的前6位代表。然后将得到的八位数分配给一个6位的结构比特域成员。右移操作符是>>,不要与cout对象的提取操作符混淆。

假设该结构的6位字段成员是,s3.a,那么字符'd'的前6位被提取如下:

unsigned char ch1 = 'd';

    ch1 = ch1 >>2;

    s3.a = ch1;

s3.a的值现在可以用于base64字母阵列的索引。

从3个字符产生第二个六位数

第二个六位由第一个八位数的最后两位和第二个八位数的后4位组成。我们的想法是让最后两个比特进入其八位数的第五和第六位,并使八位数的其余比特为零;然后将其与第二个八位数的前四个比特右移到其末端。

将最后两个比特左移到第五和第六位是由比特级左移操作符<<完成的,它不能与cout插入操作符混淆。下面的代码段将'd'的最后两个比特左移到第五和第六位。

 unsigned char i = 'd';

    i = i <<4;

此时,空出的位已经被填上了0,而不需要的非空出的移位位还在那里。为了使i中的其余位为零,i必须与00110000进行位相乘,也就是整数96。下面的语句可以做到这一点。

 i = i & 96;

下面的代码段,将第二个八位数的前四位移到最后四位的位置。

 unsigned char j = 'o';

    j = j >>4;

空出的位已经被填上了零。在这一点上,i有8位,j有8位。这两个无符号字符中的所有1现在都在它们的正确位置。为了得到字符,对于第二个六位数,这两个8位的字符必须进行位元和,如下所示。

 unsigned char ch2 = i & j;

ch2仍然有8位。为了让它变成6位,它必须被分配到一个6位的结构位域成员。如果该结构位域成员是s3.b,那么赋值将按如下方式进行。

 s3.b = ch2;

此后,s3.b将被用来代替ch2来索引base64字母阵列。

为第三个六位数添加两个零

当要编码的序列有两个字符时,第三个六位数需要添加两个零。假设一个八位数已经有了两个零的前缀,接下来的四个位是右边的位。为了使这个八位数的最后两个比特成为两个零,需要将八位数与1111100进行比特化,也就是整数252。下面的语句可以做到这一点。

 unsigned char ch3 = octet & 252;

ch3现在有所有的最后六位,这是需要的位,尽管它仍然由8位组成。为了使它变成6位,它必须被分配给一个6位的结构位域成员。如果该结构位域成员是s3.c,那么赋值将按如下方式进行。

 s3.c = ch3;

从今以后,s3.c将被用来代替ch2来索引base64字母阵列。

其余的位处理可以按照本节中的解释进行。

Base64 字母数组

对于编码来说,这个数组应该是这样的。

 unsigned char arr[] = {'A', 'B', 'C', - - - '/'};

解码则是相反的过程。所以,这个结构应该使用一个无序的地图,类似于。

 unordered_map<unsigned char, unsigned char> umap = {{'A', 0}, {'B', 1}, {'C', 2}, - - - {'/', 63}};

字符串类

字符串类应该用于全部未编码和编码的序列。其余的编程是正常的C++编程。

总结

Base64是一个由64个字符组成的字符集,其中每个字符由6个比特组成。为了进行编码,原始字符串的每一个三字节都被转换为四个六位数,每个六位数。这些六元组被用作base64字母表的索引,用于编码。如果序列由两个字符组成,仍然会得到四个六元组,最后一个六元组是数字61。如果序列由一个字符组成,仍然可以得到四个六元组,最后两个六元组是数字61的两个。

解码则相反。