Base64编码及使用

一.介绍

Base64是一种基于64个可打印字符来标识二进制数据的编码方式。它是一种可逆的编码方式。
由于2的6次方等于64,所以每6个bit作为一个单元,对应某个可打印字符。3个字节(byte)有24个bit,对应4个Base64单元,即3个字节需要用4个可打印字符来表示。

在网络传输中,除了传输普通的英文字符,也可能需要传输特殊语言字符、图片文件等,对此可以Base64编码后再进行传输。目前Base64已经成为网络上常见的传输8Bit字节代码的编码方式之一。

Base64最常见的应用是:可以将不可打印的二进制数据经过Base64编码转成可打印的字符串,这样就可以用明文来保存或展示二进制数据。

二.Base64的原理

1.Base64编码

Base64的原理超级简单,相信我们都知道ASCII 编码,从A-Z、a-z、0-9和一些其他的特殊字符,这些字符都有唯一的一个数字来表示。比如说a是97,A是65。我们来截取一部分图看一下:

同理Base64也有这样一套编码。范围是”A-Z“、”a-z“、”0-9“、”+“、”/“一共64个字符。我们给出一个表格来看一下,这个比ASCII编码要简单多了,只有64个。

由于索引是从0开始,所以最后的索引是63,在编码的时候Base64就是通过上面的进行转换编码的。这是标准的Base64协议规定,在日常使用中我们还会看到“=”或“==”号出现在Base64的编码结果中,“=”在此是作为填充字符出现,后面会讲到。

2.编码过程

这里以字符串进行Base64编码为例:

  1. step1:将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
  2. step2:将上面的24个二进制位每6个一组,共分为4组。
  3. step3:在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
  4. step4:根据Base64编码对照表(见上图)获得对应的值。

从上面的步骤我们发现:

  • Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文大约三分之一。
  • 为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。

3.示例说明

以下图的表格为示例,我们具体分析一下整个过程。

  1. step1:“M”、“a”、”n”对应的ASCII码值分别为77,97,110,对应的二进制值是01001101、01100001、01101110。如图第二三行所示,由此组成一个24位的二进制字符串。
  2. step2:如图红色框,将24位每6位二进制位一组分成四组。
  3. step3:在上面每一组前面补两个0,扩展成32个二进制位,此时变为四个字节:00010011、00010110、00000101、00101110。分别对应的值(Base64编码索引)为:19、22、5、46。
  4. step4:用上面的值在Base64编码表中进行查找,分别对应:T、W、F、u。因此“Man”Base64编码之后就变为:TWFu

4.位数不足情况

上面是按照三个字节来举例说明的,如果字节数不足三个,那么该如何处理?

  • 一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”;
  • 两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;

5.延伸

  • 大多数编码都是由字符串转化成二进制的过程,而Base64的编码则是从二进制转换为字符串,与常规恰恰相反。
  • Base64编码主要用在传输、存储、表示二进制领域,不能算得上加密,只是无法直接看到明文。也可以通过打乱Base64编码来进行加密。
  • 中文有多种编码(比如:utf-8、gb2312、gbk等),不同编码对应Base64编码结果都不一样。

三.Java中使用Base64

Java的JDK已经封装好了Base64的实现,使用的时候直接调用即可,不需要重复去造轮子。具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.nio.charset.StandardCharsets;
import java.util.Base64;


public class JdkBase64Test {

public static void main(String[] args) {
String source = "java";
String encode = Base64.getEncoder().encodeToString(source.getBytes(StandardCharsets.UTF_8));
System.out.println(encode);

byte[] decode = Base64.getDecoder().decode(encode);
System.out.println(new String(decode, StandardCharsets.UTF_8));
}
}

除了JDK的实现,还有很多开源工具也有Base64的具体实现,比如Commons Codec和Bouncy Castle,都能提供Base64的编码解码,只需要导入相应的jar包即可。下面以我在编码中用的最多的Commons Codec的示例:

在pom.xml文件中引入依赖:

1
2
3
4
5
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>

java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;


public class Base64Test {

public static void main(String[] args) {
String source = "java";
Charset charset = StandardCharsets.UTF_8;

String encode = Base64.encodeBase64String(source.getBytes(charset));
System.out.println("encode result: " + encode);

byte[] decodeBytes = Base64.decodeBase64(encode.getBytes(charset));
String decode = new String(decodeBytes, charset);
System.out.println("decode result: " + decode);
}
}

注意:JDK1.8以下自带的Base64实现效率低,1.8及以上版本效率较高。如果JDK1.8以下版本建议使用第三方的。

------ 本文完 ------