之前研究过 base64 编码,但是 blog 一直放着没写,现在把它完结了吧~
引言
base64 是一种简单又常用的编码方式,通常用于传输一些数据(可以是文本也可以是图片)。
之前在工作中,有看到相关的应用,有点好奇它的原理,就分析了一下。
规则
简而言之,base64 编码的过程就是将源字符串每 3 个字符扩展为 4 个编码表字符,从而组成一个新的字符串,其中 3 个字符对应 24 位,若取 6 位为一个编码字符,则正好对应 4 个字符。如果剩下的字符不足 3 个字符,那么就用“=”进行替代,所以,base64 编码后的字符串,可能会出现 0 个、1 个或 2 个“=”。
从这里也可以看出,因为是按 6 位进行编码的,所以这也是叫 base64 的由来😂。
对应的编码表:
码值 | 字符 | 码值 | 字符 | 码值 | 字符 | 码值 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
有了编码表之后,就可以进行编码操作了,以“A”这个单字符组成的字符串为例,字符“A”的二进制形式为:
$$ (0100 0001)_2 $$
取出前六位:
$$(0100 00)_2$$
按照编码表就等于:
$$(16)_{10} = Q$$
这样,就得到了编码后的第一个字符。但此时会发现,字符“A”还剩下“01”两位没有用到,这里直接在后面补零就好,可得0100 00
这六位,对应编码同样还是Q
。
这时,得到了由Q
这个字符构成的字符串QQ
,离目标 4 个字符还差两个,这里再直接用“=”补齐,就得到:QQ==
这个编码后的字符串了。
整体来看,编码过程并不复杂,对吧,下面再来实现它。
编码
考虑到整个编码过程中,存在着大量的取位操作,直接使用位运算取出特定的位,然后根据编码表转换为特定的字符会比较方便,所以我们直接使用 C 语言的位运算来实现,测试代码如下: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
const char enc_map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void base64_enc(const char *src, int src_len, char *enc) {
int i = 0, j = 0;
for(; i < src_len - src_len % 3; i += 3) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[((src[i] << 4) & 0x30) + ((src[i + 1] >> 4) & 0xF)];
enc[j++] = enc_map[((src[i + 1] << 2) & 0x3C) + ((src[i + 2] >> 6) & 0x3)];
enc[j++] = enc_map[src[i + 2] & 0x3F];
}
if(src_len % 3 == 1) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[(src[i] << 4) & 0x30];
enc[j++] = '=';
enc[j++] = '=';
} else if(src_len % 3 == 2) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[((src[i] << 4) & 0x30) + ((src[i + 1] >> 4) & 0xF)];
enc[j++] = enc_map[(src[i + 1] << 2) & 0x3C];
enc[j++] = '=';
}
}
int main() {
char *strSrc[10] = {"A", "AB", "ABC", "ABCD", "Killing"};
char strEnc[1024];
memset(strEnc, 0, sizeof(strEnc));
for(int i = 0; i < 5; ++i) {
base64_enc(strSrc[i], strlen(strSrc[i]), strEnc);
printf("%s\n%s\n", strSrc[i], strEnc, strDec);
}
return 0;
}
/*
output:
A
QQ==
AB
QUI=
ABC
QUJD
ABCD
QUJDRA==
Killing
S2lsbGluZw==
*/
解码
学会如何编码之后,对应的解码自然也不在话下了,只需要按照对应的解码表进行解码即可,我们直接在上面的代码中修改: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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
const char enc_map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char dec_map[256] = {0};
void init() {
unsigned int idx = 0;
for(unsigned int i = 0; i < strlen(enc_map); ++i)
dec_map[enc_map[i]] = idx++;
}
void base64_enc(const char *src, int src_len, char *enc) {
int i = 0, j = 0;
for(; i < src_len - src_len % 3; i += 3) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[((src[i] << 4) & 0x30) + ((src[i + 1] >> 4) & 0xF)];
enc[j++] = enc_map[((src[i + 1] << 2) & 0x3C) + ((src[i + 2] >> 6) & 0x3)];
enc[j++] = enc_map[src[i + 2] & 0x3F];
}
if(src_len % 3 == 1) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[(src[i] << 4) & 0x30];
enc[j++] = '=';
enc[j++] = '=';
} else if(src_len % 3 == 2) {
enc[j++] = enc_map[(src[i] >> 2) & 0x3F];
enc[j++] = enc_map[((src[i] << 4) & 0x30) + ((src[i + 1] >> 4) & 0xF)];
enc[j++] = enc_map[(src[i + 1] << 2) & 0x3C];
enc[j++] = '=';
}
}
void base64_dec(const char *enc, int enc_len, char *dec) {
if(enc_len % 4 != 0) {
printf("encoding str is valid.\n");
exit(0);
}
int i = 0, j = 0;
for(; i < enc_len; i += 4) {
if(!dec_map[enc[i]] || !dec_map[enc[i + 1]] || !dec_map[enc[i + 2]] || !dec_map[enc[i + 3]])
break;
dec[j++] = (dec_map[enc[i]] << 2) + ((dec_map[enc[i + 1]] >> 4) & 0x3);
dec[j++] = ((dec_map[enc[i + 1]] << 4) & 0xF0) + ((dec_map[enc[i + 2]] >> 2) & 0xF);
dec[j++] = ((dec_map[enc[i + 2]] << 6) & 0xC0) + (dec_map[enc[i + 3]] & 0x3F);
}
if(i < enc_len) {
if(enc[i + 2] == '=' && enc[i + 3] == '=') {
dec[j] = (dec_map[enc[i]] << 2) + ((dec_map[enc[i + 1]] >> 4) & 0x3);
} else if(enc[i + 3] == '=') {
dec[j++] = (dec_map[enc[i]] << 2) + ((dec_map[enc[i + 1]] >> 4) & 0x3);
dec[j++] = ((dec_map[enc[i + 1]] << 2) & 0xF0) + ((dec_map[enc[i + 2]] >> 2) & 0xF);
}
}
}
int main() {
init();
char *strSrc[10] = {"A", "AB", "ABC", "ABCD", "Killing"};
char strEnc[1024], strDec[1024];
memset(strEnc, 0, sizeof(strEnc));
memset(strDec, 0, sizeof(strDec));
for(int i = 0; i < 5; ++i) {
base64_enc(strSrc[i], strlen(strSrc[i]), strEnc);
base64_dec(strEnc, strlen(strEnc), strDec);
printf("%s\n%s\n%s\n", strSrc[i], strEnc, strDec);
}
return 0;
}
/*
output:
A
QQ==
A
AB
QUI=
AR
ABC
QUJD
ABC
ABCD
QUJDRA==
ABCD
Killing
S2lsbGluZw==
Killing
*/
结语
这篇文章重点分析了 base64 编码、解码的过程,并用 C 语言简单实现了一个 demo 程序。总体来看,base64 编码的过程并不复杂,作为理解计算机编码方式的练习来讲,还不错。
PS:欠了一年的 blog 终于水完了~😂