之前研究过 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 语言的位运算来实现,测试代码如下:
Ver11 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
| #include <stdio.h> #include <stdlib.h>
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; }
|
解码
学会如何编码之后,对应的解码自然也不在话下了,只需要按照对应的解码表进行解码即可,我们直接在上面的代码中修改:
Ver21 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
| #include <stdio.h> #include <stdlib.h> #include <string.h>
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; }
|
结语
这篇文章重点分析了 base64 编码、解码的过程,并用 C 语言简单实现了一个 demo 程序。总体来看,base64 编码的过程并不复杂,作为理解计算机编码方式的练习来讲,还不错。
PS:欠了一年的 blog 终于水完了~😂