封面画师:藤原海藻(微博)     封面ID:战双帕弥什 2020-10-23 更新封面图

0. 前言

最近肠胃不是很好,一去厕所就呆半天。一蹲下,非得等到腿麻才愿意起身,甚是恼火。

前几日,再次光临厕所,瞎待着也不是,拿起手机就划了起来。

忽而看到一视频,讲述 Base64 编码,有感少许,后而略做研究,遂记此文。

1. 什么是 Base64

先看一下 Wiki 是怎么说的:

Base64维基百科

简单来说,Base64 就是用 64 个可打印字符来表示二进制数据的方法。

为什么叫 Base64,不叫 Base32?或者 Base128呢?

6 比特为一单元,6 比特可以表示 64 个数,因此就叫 Base64。

那为什么要用 Base64 来表示二进制呢?

首先要知道 Base64 是一种可逆的编码方式,说到编码方式不禁想到:GBK、GB2312、UTF-8 等编码。

计算机只认识二进制数据,怎么识别用户书写的内容呢?

将书写的内容转成二进制不就完了吗?而这就需要用到编码。

所谓编码就是将信息从一种形式或格式转换为另一种形式的过程。

但是世界上有很多语言,每种语言有自己对应的文字,这些文字的编码方式可能不同,因此也就造成了多种编码的出现。

初学某一门编程语言时,一定会接触到 ASCII 码,这也是一种编码,这种编码有什么用?

我们再看看 Wiki:

ASCII维基百科

原来 ASCII 码是一种基于拉丁字母的编码,是主要用于显示现代英语的。

那么问题来了,假设当前设备系统只能使用 ASCII 码,而对于非 ASCII 字符咋办?

这个时候就可以使用 Base64 将非 ASCII 码转换成 ASCII 码。

当然这只是最基本的应用,Base64 还有其他的作用,比如用于在某些只支持纯文本传输的协议(SMTP 协议)下传输一张图片等。具体还有哪些作用可以自行百度或 Google 一下。

2. Base64 原理

Base64 编码步骤

  1. 确认被编码字符串的编码(英文使用 ASCII 码,中文通常使用 GB2312);
  2. 按照原本的编码求出这些字符串对应的编码的二进制数据,3 字节一组;
  3. 一字节 8 bit,8 个二进制位,3 字节就有 24 个二进制位。将 24 个二进制位进行分组,每组 6 个二进制位,一共 4 组,并在每组二进制位前添加两个 0,重新形成一个字节(将 3 字节转换成 4 字节);
  4. 求出重新分组后每组二进制数据对应的十进制数据;
  5. 按照下表,得出十进制数据对应的字符,最后将求出的字符合并为字符串,得出的字符串就是被编码字符串经过 Base64 编码后得到的数据。
Base64编码表

上述步骤是被编码字符串恰好可以被分成若干个 3 个字节一组的情况,那如果分组后多出一个字节或两个字节呢?

如果多出一个字节,需要补上两个字节,补上的两个字节数据全是 0,最后求出 Base64 的编码结果时要在末尾加上两个 =;如果多出两个字节,要补上一个字节,补上的一个字节数据全是 0,最后求出 Base64 的编码结果时在末尾加上一个 =

简单来说:把 3 个 8 位字节(3 * 8 = 24)转化为 4 个 6 位字节(4 * 6 = 24),之后在每个 6 位的前面补两个 0,形成 8 位,即一个字节。再根据每个字节的值,用 Base64 编码表对应的字符替换。原数据差几个字节到 3 字节的整数倍,最终编码末尾补几个 =

下面演示一下:

英文的 Base64 编码

字符串“fan”经过 Base64 编码后会得到什么呢?

fan的Base64编码

可以看出,“fan”的 Base64 编码结果为:ZmFu,也可以利用在线工具测试一下,最终得出的结果和我们的结果一致的。

在线工具地址:Base64 在线编码解码

那数据无法分成 3 个字节一组呢?

字符串“mo”经过 Base64 编码后会得到什么呢?

mo的Base64编码

由于“mo”对应的 ASCII 码只有两个字节,要想凑齐三个字节,需要补一个全为 0 的字节,因此 Base64 最终编码结果要在末尾添加一个 =。编码结果为:bW8=

同样,如果原数据只有一个字节,就需要补两个全为 0 的字节,最终编码结果末尾添加 ==,如:

m的Base64编码

如果被编码字符按 3 个字节一组分成了不止一组,只需要最后将结果拼起来就行了,比如:

“mofan”经过 Base64 的编码结果为:bW9mYW4=

上面是英文使用 Base64 进行编码的过程,那么中文呢?

中文有很多编码方式,在使用 Base64 编码之前,需要先知道中文当前是什么编码,之后的步骤就和上述步骤一样了。

中文的 Base64 编码

假设 当前系统上 有一字符串“默烦”,需要使用 Base64 将其表示出来,应该怎么做呢?

首先得知道中文字符在当前操作系统中对应的字符编码表代码是多少,这样可以确定当前系统的编码。在 Windows 系统下打开命令行提示工具(快捷键 win + r,输入 cmd 即可代开),然后输入 chcp

chcp

然后百度一下【活动代码页 936】可知,当前系统使用的编码是:GB2312

然后在 GB2312 编码表中找到“默烦”两字对应的十六进制编码:

默的GB2312编码

PS:在线工具:GB2312简体中文编码表

可以看到“默”处在行名为 C4A0 列名为 +C 的格子里,将 C4A0 + C 可得:C4AC,这就是“默”对应的 GB2312 编码。

使用相同的方式,求得“烦”对应的 GB2312 编码为:B7B3

将这两个十六进制的数转换成二进制(可以使用电脑自带的计算机,打开程序员模式)可得:

1
1100 0100 1010 1100 1011 0111 1011 0011

前 16 位是 C4AC 的二进制,后 16 位是 B7B3 的二进制,我们将其合在一起。

我们对这些数据进行分组,每 6 个一组,不够 6 位末尾加 0 凑齐 6 位,然后将这 6 位看成一个二进制数,计算其十进制数:

1
2
3
4
5
6
110001 --> 49
001010 --> 10
110010 --> 50
110111 --> 55
101100 --> 44
110000 --> 48

最后利用求得的十进制根据 Base64 编码表求出对应的字符,将这些字符拼接起来。

求出的字符串为:xKy3sw

由于提供的数据只有 4 个字节,需要补上 2 个全为 0 的字节,因此最终编码结果末尾要添加上 ==

那么,“默烦”两字在 GB2312 编码下使用 Base64 编码求得的编码是:xKy3sw==

注意: 由于中文有不同的编码,因此在不同编码下的中文使用 Base64 编码会得到不一样的结果。比如,UTF-8 编码下的“默烦”经过 Base64 编码后得到的结果是 6buY54Om,而 GB2312 编码下是 xKy3sw==

3. 简单应用

Java 中使用 Base64

明白的原理,应该怎么使用代码来实现呢?自己手写逻辑,也不是不行,但在 Java8 中 Base64 编码已经成为 Java 类库的标准。Java 8 内置了 Base64 编码的编码器和解码器。

具体使用可以参考:Java8 Base64

前面求得了在 GB2312 编码下“默烦”对应的 Base64 编码,尝试写个程序验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
// 解码器
BASE64Decoder decoder = new BASE64Decoder();
// 编码器
BASE64Encoder encoder = new BASE64Encoder();

try {
String source = "默烦";
byte[] bytes = source.getBytes("GB2312");
String str = encoder.encodeBuffer(bytes);
System.out.println(str);
String s = new String(decoder.decodeBuffer("xKy3sw=="), "GB2312");
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}

运行后,控制台输出如下:

Java中测试Base64编码和解码

输出结果和预想的一致,证明最初的计算没有错。


题外话:如何转换汉字的编码?可以使用以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author 默烦 2020/10/23
*/
public class EncodingConversion {
public static void main(String[] args) throws UnsupportedEncodingException {
// 源码文件是 UTF-8 格式,转换为 String 变成 unicode 格式
String utf8Str = "默烦";
//利用 getBytes 将 unicode 字符串转成 GB2312 格式的字节数组
byte[] gbBytes = utf8Str.getBytes("GB2312");
//然后用 GB2312 对这个字节数组解码成新的字符串
String gbStr = new String(gbBytes, "GB2312");
System.out.println(gbStr); // 输出 GB2312 编码下的“默烦”
}

// 抽取为方法
private static String unicodeToGB2312 (String s) throws UnsupportedEncodingException {
return new String(s.getBytes("GB2312") , "GB2312");
}
}

如果想转其他的编码,只需要修改编码即可。

图片使用 Base64 编码

最开始就说了,Base64 是一种表示二进制数据的方法,图片也是二进制文件,那么图片可以使用 Base64 编码吗?

当然可以,只需要利用 Java 中的 IO 流将图片读入到内存中,然后使用 Java8 提供的 Base64 编码器就可以将图片转成 Base64 的编码格式了。

经过前面的测试可以发现在使用 Base64 编码后,3 个字节会变成 4 个字节,字节数会增加,因此不建议将太大的图片进行 Base64 编码(图片大小在 300kb 以内为最佳)。

可以使用以下程序将图片进行 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
/**
* @author 默烦 2020/10/22
*/
public class Base64Test {
public static void main(String[] args) {
// 解码器
BASE64Decoder decoder = new BASE64Decoder();
// 编码器
BASE64Encoder encoder = new BASE64Encoder();

try {
String img = encoder.encode(getFileBytes("D:\\pic.png"));
System.out.println("data:image/png;base64," + img);
// <img src="data:image/jpg;base64,"/>
} catch (Exception e) {
e.printStackTrace();
}
}

private static byte[] getFileBytes(String filePath) {
byte[] r = new byte[0];
BufferedInputStream bis = null;
try {
FileInputStream fileInputStream = new FileInputStream(filePath);
// 文件不存在时
if (fileInputStream.available() < 1) {
return new byte[0];
}
byte[] bytes = new byte[1024];
r = new byte[fileInputStream.available()];

int len;
bis = new BufferedInputStream(fileInputStream);
int pos = 0;
while ((len = bis.read(bytes, 0, bytes.length)) != -1) {
System.arraycopy(bytes, 0, r, pos, len);
pos += len;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return r;
}
}

那这有什么用?

在 HTML 下一般是这样插入图片的:

1
<img src="图片路径" />

图片经过 Base64 编码后,可以将图片路径换成经过 Base64 编码后的数据。只不过还需要添加前缀,可以百度搜索【src引用base64】,简单来说:

如果原图片格式是 png 的,可以使用:

1
<img src='data:image/png;base64,' />

除此之外,还有如下前缀可供选择:

1
2
3
4
5
6
7
8
9
10
11
12
data:,文本数据
data:text/plain,文本数据
;javascript:;,HTML代码
;javascript:;;base64,base64编码的HTML代码
data:text/css,CSS代码
data:text/css;base64,base64编码的CSS代码
data:text/javascript,Javascript代码
data:text/javascript;base64,base64编码的Javascript代码
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
data:image/x-icon;base64,base64编码的icon图片数据

将图片经过 Base64 编码后的数据按上述要求复制到 txt 中,然后保存,修改拓展名为 html,然后双击打开就可以显示图片。

4. Base64 是加密?

前文已经介绍了 Base64 的基本原理,还进行了简单的应用,但在有些地方可以看到说 Base64 是一种加密算法,那这对吗?

肯定是不对的,全篇都在说 Base64 编码,Base64 是编码,不是加密。

所谓加密,是将明文变为一种不易破解的密文,提高识别难度,而编码是将信息从一种形式或格式转换为另一种形式的过程。

既然已经知道了 Base64 编码的原理,那这破解也太简单了?


参考链接:

为什么要使用base64编码,有哪些情景需求?

Base64编码原理与应用

base64编码有什么用?(base64介绍及应用)