Base 64 个人理解笔记

Base 64 加密标准码表

Base64加密过程

原理

我们将字符串中的字符每3个一分割,再将3个字符转换为每个8位数长的2进制数据。

如输入:ABC,则转换的二进制是:A : 01000001、B : 01000010、C : 01000011

我们将这3个二进制的数据拼在一起得到一个全新的二进制数据

010000010100001001000011

我们再对其每六个一分组,并在最高位补上00

分组出来为 010000 010100 001001 000011,补上00后为 00010000 00010100 00001001 00000011

之后我们对其转化为10进制

可以得到 16 20 19 3

再对应开头的标准码表进行 ” 翻译 “ 得到新的字符,再将他们拼在一起就是我们想要得到的加密后的数据了。

可以得到 Q U J D

可是如果我们的字符不是3的倍数怎么办呢?

我们以 Basic 来进行类推,Basic一共是5个字符我们按照相同的步骤来进行

B:01000010 a:01100001 s:01110011 i:01101001 c:01100011

进行拼接得到 0100001001100001011100110110100101100011

再进行每六个一组得到 010000 100110 000101 110011 011010 010110 0011

这个时候我们就会发现最后面怎么只有4个数字呢?

我们可以发现一个规律( 3 * 8 = 4 * 6 )二进制数是长度为 8 ,而我们是按照每 6 个数字一组,所以我们每 3 个字符一起就可以转换成 4组数据。但是我们按照之前的最高位加上 00 后最后一个也才是 000011 不是 8位长度,这个时候我们就要再末尾补上 0 让其变为 8 位的二进制数,我们也就得到了 00001100

这个时候我们便可以按照之前的法则进行转换,我们可以得到 : 00010000001001100000010100110011000110100001011000001100

我们把上面的数据分一下组:

00010000 00100110 00000101 00110011 00011010 00010110 00001100

转换为 10 进制可以得到:16 38 5 51 26 22 12

对照码表我们得到了:QmFzaWM

这个时候如果你去在线的网站上转换的话你会发现后面多出来一个 = ,这又是为什么呢?

一般而言我们在进行编码加密的时候会遇到余下两个或者一个字符,这个时候我们就会在末尾补上对应个数的 0 使之成为 8 位的二进制数,因为我们在输入之前便已经得到了字符串Basic的长度为 5 ,那么我们便可以判断出会有几个字符串剩余,每有一个字符串剩余我们就会在最后的密文里面加入对应数量的 =

实现方法

Base64在对输入的数据进行加密时是以每三个字符为一组,进行对应的位运算,如果字符剩余两个则在对这两个数据进行位运算加密后的密文补上一个=,如果剩余一个字符在同样的位运算加密后的密文补上两个=

我们同样以ABC为例子,用位运算实现上面的一般原理

A:A -> Q

那么这个要怎么实现呢?我们先观察两者的二进制数

A 的二进制数是:01000001 Q的二进制数是:00010000

不难看出 A在右移两位后便得到了Q

B:B -> U J

B 的二进制数是:01000010,可是我们是将A的二进制数的最后两位与B的二进制数的开头四位组成的一个新的二进制数在在最高位补上 00 得到00010100 ( U )再把 B 的最后两位二进制数与 C 的前四位二进制数组成00001001 ( J )

这个要怎么实现呢?
U :

我们借用与运算(&)的规则,两个操作对象同一位都为 1 则结果对应位为 1 ,否则对应结果为 0,我们要取最后的两位那么我们可以借用 0x3 (3)

0x3的二进制数是:00000011 在和A的二进制进行与运算后可以得到:00000001,我们要在最高位加上00,并且第3、4位是A的最后两位二进制数,而现在的二进制数是:00000001,那么我们再次移位向左移 4 位得到:00010000

接下来我们还要取B的二进制数的前4位0100,同样的我们将B右移4位便可以得到:00000100,但是我们要把A的后两位二进制与B的前4位二进制拼在一起,这个时候又该怎么办呢?

我们借助或运算( | ),若两个操作对象同一位都为 0,则结果对应位为 0,否则对应位为 1 。

我们把0001000000000100进行或运算便得到了:00010100 转化成 10进制为 20,对应码表为 U

J :

按照上述步骤我们将 B 与 0xf (00001111) 进行与运算得到 B 的二进制数最后 4 位在左移 2 位,C 则右移 6 位得到其最后两位二进制数,将两个数进行或运算即可得到:00001001转化成 10进制为 9,对应码表为 J

C : C -> J D
D :

对于 D 而言我们仅需要去取二进制数的后 6 位就可以了,那么我们将 C 和 0x3f进行与运算便可以得到00000011转化成 10进制为 3,对应码表为 D

此时我们便完成了对应数据的加密过程,那么在代码上是如何实现的呢?

代码如下 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
unsigned char *base64_encode(unsigned char *str)  
{
long len;
long str_len;
unsigned char *res;
int i,j;
//定义base64编码表
unsigned char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

//计算经过base64编码后的字符串长度
str_len=strlen(str);
if(str_len % 3 == 0)
len=str_len/3*4;
else
len=(str_len/3+1)*4;

res=malloc(sizeof(unsigned char)*len+1);
res[len]='\0';

//以3个8位字符为一组进行编码
for(i=0,j=0;i<len-2;j+=3,i+=4)
{

res[i]=base64_table[str[j]>>2]; //取出第一个字符的前6位并找出对应的结果字符

res[i+1]=base64_table[(str[j]&0x3)<<4 | (str[j+1]>>4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符

res[i+2]=base64_table[(str[j+1]&0xf)<<2 | (str[j+2]>>6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符

res[i+3]=base64_table[str[j+2]&0x3f]; //取出第三个字符的后6位并找出结果字符

}

switch(str_len % 3)
{
case 1:
res[i-2]='=';
res[i-1]='=';
break;
case 2:
res[i-1]='=';
break;
}

return res;
}

我们加密一般是采用每 3 个一加密 ,而对于这 3 个字符来说,进行的操作都是同质化的,可以按照上面的模板进行加密,因而可以快速完成加密。

解密过程 :

原理

在加密过程中我们了解到了( 3 * 8 = 4 * 6 )的规律,那么我们再次利用这个规律将其还原回去,不过这次便是以 4 位为一组,来进行解密,因为 = 的加入,所以都能按照 4 个一组进行分配。按照上面的码表进行查找其对应的 10 进制数据,我们再将 10 进制数据转换为8 位长度的二进制数据之后去除最高位的 00 进行拼接,得到一串新的二进制数据。我们对这串数据按每 8 个一组分割,便可以得到对应明文中字符对应的二进制数据,最后进行转换和拼接,便是明文。

其中在解密过程中我们将 = 对应的二进制为00000000,不过一般在解密过程中我们会忽略 = ,将字符串进行解密。(python 中如果密文数据不为 4 的倍数会报错)

我们仍以 ABC 为例子,ABC转换出的数据是 QUJD

我们逆向查找码表可以得到对应的数据分别为:16 20 19 3

我们依次转换为二进制并去除最高位的 00 得到:010000 010100 001001 000011

我们可以看到现在的数据就是 6 位二进制数据了我们按照加密过程的逆向进行操作将上述数据拼接,每 8 个数字一组可以得到:01000001 01000010 01000011,我们将这些数据还原成字符便可以得到明文 ABC

实现方法

因为最终的密文中,如果 6 位二进制数据的分组不满 4 组,会有 = 作为填充物,所以一个 Base64 完后的密文总是能够被 4 整除。所以,在解密中,我们每次需要处理 4 个字符,将这 4 个字符编码之后转换成十进制,再转换成二进制,不足 6 位的高位补0,然后将 6 个位一组的二进制数按原顺序重新分成每 8 个位一组,也就是一个字节一组。然后将其转换成十六进制,再转换成对应的字符。

我们以 Basic 为例子进行翻译明文,我们知道密文是 QmFzaWM=
我们分一下组 QmFz aWM=

B :

我们观察 Q 的二进制:00010000,Q是由 B 的前六位二进制数转换而来的,而 B 的二进制数是:01000010
我们将 Q 的二进制数进行左移 2 位可以得到 01000000,我们发现与 B 的二进制数据还是有区别,此时我们再观察下一位 m 的二进制数据:00100110 ,这个时候我们如果将 m 右移4位那么不就可以得到我们想要的 00000010,再将其与01000000 进行或运算那不就得到第一个字符 B 了吗。

a :

我们还是一样的观察 m 的二进制:00100110,m 是由 B 的后 2 位二进制数加上 a 的前 4 位组成的,我们观察 a 的二进制数:01100001 和 F 的二进制数:00000101 可以看到我们将 m 的二进制数左移 4 位可以得到 01100000,此时我们再将 F右移两位可以得到:00000001,这个时候我们将这两个数进行或运算便可以得到我们想要的 a 的二进制数了。

s:

s是 3 个为一组的字符里面的最后一个,s将组成 F 、z,那么我们解密 Fz 之前,我们还是先来观察一下 z 的二进制:00110011, s 的二进制:01110011,F 的二进制数:00000101,如果你仔细观察,那么你会发现我们将 F 左移 6 位后在与 z 进行与运算便是 s 的二进制了。

a、W、M、=:

解密类似于 QmFz 就不再过多叙述了,最后我们会遇到 = ,此时我们不需要管 = ,将我们所有解密后的字符组合成一个字符串那么我们便得到了明文。

此时我们便完成了对应数据的解密过程,那么在代码上是如何实现的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

unsigned char *base64_decode(unsigned char *code)
{
//根据base64表,以字符找到对应的十进制数据
int table[]={0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long len;
long str_len;
unsigned char *res;
int i,j;

//计算解码后的字符串长度
len=strlen(code);
//判断编码后的字符串后是否有=
if(strstr(code,"=="))
str_len=len/4*3-2;
else if(strstr(code,"="))
str_len=len/4*3-1;
else
str_len=len/4*3;

res=malloc(sizeof(unsigned char)*str_len+1);
res[str_len]='\0';

//以4个字符为一位进行解码
for(i=0,j=0;i < len-2;j+=3,i+=4)
{
res[j]=((unsigned char)table[code[i]])<<2 | (((unsigned char)table[code[i+1]])>>4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j+1]=(((unsigned char)table[code[i+1]])<<4) | (((unsigned char)table[code[i+2]])>>2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j+2]=(((unsigned char)table[code[i+2]])<<6) | ((unsigned char)table[code[i+3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}

return res;

}

参考资料

C语言实现Base64编码/解码_开挂的熊猫-CSDN博客(https://blog.csdn.net/qq_26093511/article/details/78836087)

Base64编码 - 简书 (https://www.jianshu.com/p/e95278ed98b4)


Base 64 个人理解笔记
https://equinox-shame.github.io/2022/03/14/Base 64个人理解笔记/
作者
梓曰
发布于
2022年3月14日
许可协议