base64实现及细节剖析

源码

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<stdio.h>
#include <string.h>
char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void base64_encode(char raw[], char encode[])
{
int code_len = strlen(raw);
int final_len = 0;
if (code_len % 3)
{
final_len = (code_len / 3 + 1) * 4;
}
else
final_len = (code_len / 3) * 4;
int i = 0, i_ = 0;
for (; i < code_len; i += 3, i_ += 4)
{
encode[i_] = base64_table[raw[i] >> 2];
encode[i_ + 1] = base64_table[((raw[i] & 0x03) << 4) | ((raw[i + 1] & 0xf0) >> 4)];
encode[i_ + 2] = base64_table[((raw[i + 1] & 0x0f) << 2) | ((raw[i + 2] & 0xc0) >> 6)];
encode[i_ + 3] = base64_table[raw[i + 2] & 0x3f];
}
if (code_len % 3 == 1)
{
encode[i_ - 1] = '=';
encode[i_ - 2] = '=';
}
else if (code_len % 3 == 2)
{
encode[i_ - 1] = '=';
}
return;
}
int Findindex(char c)
{
for (int i = 0; i < strlen(base64_table); ++i)
{
if (c == base64_table[i])
return i;
}
}
void base64_decode(char* encode, char* decode)
{
int decode_len=0;
int encode_len = strlen(encode);
if (strstr(encode, "=="))
decode_len = (encode_len / 4) * 3 - 2;
else if (strstr(encode, "="))
decode_len = (encode_len / 4) * 3 - 1;
else
decode_len = (encode_len / 4) * 3;
int i = 0, i_ = 0;
for (; i < encode_len; i_ += 4, i += 3)
{
decode[i] = (Findindex(encode[i_]) << 2) | (Findindex(encode[i_ + 1]) & 0x30) >> 4;
decode[i + 1] = ((Findindex(encode[i_ + 1]) & 0xf) << 4) | ((Findindex(encode[i_ + 2]) & 0x3c) >> 2);
decode[i + 2] = ((Findindex(encode[i_ + 2]) & 0x3) << 6) | ((Findindex(encode[i_ + 3])));
}
decode[decode_len] = 0;
return;
}
int main()
{
char raw[] = "hgame{123456}";
char encode[100] = { 0 };
char decode[100] = { 0 };
base64_encode(raw, encode);
printf("%s", encode);
base64_decode(encode, decode);
printf("\n%s", decode);
}

细节剖析

编码部分

记录长度

首先要先计算长度,base64就是将三个字节扩展为四个字节,所以要分成有余数和整除两种情况

1
2
3
4
5
6
7
8
int code_len = strlen(raw);
int final_len = 0;
if (code_len % 3)
{
final_len = (code_len / 3 + 1) * 4;
}
else
final_len = (code_len / 3) * 4;

编码部分

首先先介绍一下两个工具,&和|,和一些数据做&运算可以取到我们想要的位,而|运算可以将两部分结合在一起

编码后的索引最多为六位,原先的数据可以是八位的

encode[i]

这个其实是最好实现的,因为只需要取到前六位,所以直接>>2就可以实现

1
encode[i_] = base64_table[raw[i] >> 2];

encode[i+1]

先明确我们的需求,从第一个数据获取最后的两位与第二个数据获取的前四位结合。需要用到&,这里的结合就需要用到|。

首先要保证我们取到的是两位的,需要&0x03

0 0 0 0 0 0 0 1 1

因为只有最后两位是1,根据按位&,当有一个是0时运算后结果必定为0,所以就可以取到最后两位

下一步是移到正确的位置,需要用到位移运算符

这里第一个数的最后两位编码后是放在前面两位的位置,所以需要<<4

接下来取剩余的四位

剩余的四位来自第二个数的前四位

可以先&0xf0使得后四位都为0,当然也可以不用,直接>>4

最后使用|结合起来

0 0 0 1 1 0 0 1 1
0 0 0 0 0 1 0 1 1
0 0 0 1 1 1 0 1 1
1
encode[i_ + 1] = base64_table[((raw[i] & 0x03) << 4) | ((raw[i + 1]) >> 4)];

encode[i+2]

要用到第二个数据的后四位和第三个数据的前两位

同理,要保留第二个数据的后四位并且去除前四位,就需要&0xf,移到正确的位置<<2,剩余两位留给第三个数据的前两位

要取到第三个数据的前两位

可以直接>>6保证前两位移动到最后的两位,也可以先用&0xc0清除后四位,因为是00110000,最后结合起来就可以了

1
encode[i_ + 2] = base64_table[((raw[i + 1] & 0x0f) << 2) | ((raw[i + 2] ) >> 6)];

encode[i+3]

只需要取到第三个数据的后六位就可以了

直接&0x3f,因为0x3f的二进制是00111111

1
encode[i_ + 3] = base64_table[raw[i + 2] & 0x3f];

填加=

如果编码前的数据长度%3不等于0,需要使用=填充

如果多出一位的话,根据经过上述过程会变成两位,所以最后两位需要用到=来填充

多出两位,经过上述过程变成三位,所以只需填充最后一位为=

1
2
3
4
5
6
7
8
9
if (code_len % 3 == 1)
{
encode[i_ - 1] = '=';
encode[i_ - 2] = '=';
}
else if (code_len % 3 == 2)
{
encode[i_ - 1] = '=';
}

解码部分

六位还原为八位

去除=

先计算解码后的长度,每四个对应三个,最后减去=的长度

1
2
3
4
5
6
7
8
int decode_len=0;
int encode_len = strlen(encode);
if (strstr(encode, "=="))
decode_len = (encode_len / 4) * 3 - 2;
else if (strstr(encode, "="))
decode_len = (encode_len / 4) * 3 - 1;
else
decode_len = (encode_len / 4) * 3;

decode[i]

其实和上面过程正好相反,取第一个编码数据的后六位(因为前两位是填充的0)和第二个编码数据的前两位,在此之前需要先去除前两位填充的0,所以要&0x30,根据上面的过程,我们需要的是00110000,正好是0x30

再用|结合

1
decode[i] = (Findindex(encode[i_]) << 2) | (Findindex(encode[i_ + 1]) & 0x30) >> 4;

decode[i+1]

需要第二个编码数据的后四位和第三个编码数据的前四位,也是需要先去除填充的0

所以第二个数据&0xf保证取到后四位,<<4移到前四位

第三个数据&0x3c(00111100)进行去除填充的前两位0以及取到需要的那四位,再>>2,最后使用|结合

1
decode[i + 1] = ((Findindex(encode[i_ + 1]) & 0xf) << 4) | ((Findindex(encode[i_ + 2]) & 0x3c) >> 2);

decode[i+2]

第三个编码数据的后两位和第四个编码数据的那六位

和上述过程类似,就不赘述了

字符数组结束

最后记得填加字符数组结束符

1
decode[decode_len] = 0;

写在最后

base64还可以魔改,进行变表操作或者在编码过程中参杂异或(Dasctf2022)