Java Base64算法

Base64算法并不是加密算法,它的出现是为了解决ASCII码在传输过程中可能出现乱码的问题。Base64是网络上最常见的用于传输8bit字节码的可读性编码算法之一。可读性编码算法不是为了保护数据的安全性,而是为了可读性。可读性编码不改变信息内容,只改变信息内容的表现形式。Base64使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”,故得此名。

储备知识Byte和bit

Byte:字节,数据存储的基本单位;

bit:比特,也叫位,一个位只能存储0或者1。

关系:1Byte = 8bit。

一个英文字符占1个字节,8位:

1
2
3
4
5
6
7
8
9
@Test
public void demo1() {
String a = "a";
byte[] bytes = a.getBytes();
for (byte b : bytes) {
System.out.println(b);
System.out.println(Integer.toBinaryString(b));
}
}

程序输出:

1
2
97
1100001

一个中文字符在不同编码下所占的字节数不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void demo2() throws UnsupportedEncodingException {
String a = "鸟";
byte[] utf8Bytes = a.getBytes("utf-8");
for (byte b : utf8Bytes) {
System.out.print(b);
System.out.print(" ");
System.out.println(Integer.toBinaryString(b));
}
System.out.println();
byte[] gbkBytes = a.getBytes("gbk");
for (byte b : gbkBytes) {
System.out.print(b);
System.out.print(" ");
System.out.println(Integer.toBinaryString(b));
}
}

程序输出:

1
2
3
4
5
6
-23    11111111111111111111111111101001
-72 11111111111111111111111110111000
-97 11111111111111111111111110011111

-60 11111111111111111111111111000100
-15 11111111111111111111111111110001

所以在UTF-8编码下,一个中文占3个字节;在GBK编码下,一个中文占2个字节。

Base64编码原理

Base64编码表:

索引
对应字符
索引
对应字符
索引
对应字符
索引
对应字符
0
A
17
R
34
i
51
z
1
B
18
S
35
j
52
0
2
C
19
T
36
k
53
1
3
D
20
U
37
l
54
2
4
E
21
V
38
m
55
3
5
F
22
W
39
n
56
4
6
G
23
X
40
o
57
5
7
H
24
Y
41
p
58
6
8
I
25
Z
42
q
59
7
9
J
26
a
43
r
60
8
10
K
27
b
44
s
61
9
11
L
28
c
45
t
62
+
12
M
29
d
46
u
63
/
13
N
30
e
47
v
14
O
31
f
48
w
15
P
32
g
49
x
16
Q
33
h
50
y

Base64编码的过程:

  1. 将字符串转换为字符数组;
  2. 将每个字符转换为ASCII码;
  3. 将ASCII码转换为8bit二进制码;
  4. 然后每3个字节为一组(一个字节为8个bit,所以每组24个bit);
  5. 将每组的24个bit分为4份,每份6个bit;
  6. 在每6个bit前补0,补齐8bit(前面补0不影响数值大小);
  7. 然后将每8bit转换为10进制数,根据上面的Base64编码表进行转换。
上面步骤中,为什么要将每组24个bit分为4份,每份6个bit呢?因为6bit的最大值为111111,转换为十进制为63,所以6bit的取值范围为0~63,这和base64编码表长度一致。

根据上面的过程,我们来举个例子:现要对hello这个字符串进行Base64编码,过程如下:

  1. hello转换为字符数组:h e l l o;
  2. 对应的ASCII码为:104 101 108 108 111;
  3. 转换为8bit二进制数:01101000 01100101 01101100 01101100 01101111
  4. 分组,每组24个bit(不足24个bit的用00000000补齐): 011010000110010101101100 011011000110111100000000;
  5. 每组24bit分为4份,每份6bit:011010 000110 010101 101100 011011 000110 111100 000000;
  6. 在每6个bit前补0,补齐8bit:00011010 00000110 00010101 00101100 00011011 00000110 00111100 00000000;
  7. 将每8bit转换为10进制数:26 6 21 44 27 6 60 0
  8. 从上面Base64编码表中找到十进制数对应的字符(末尾的0并不是A,而是用=等号补位):a G V s b G 8 =

所以hello经过Base64编码的结果为aGVsbG8=

我们可以用代码验证下(JDK8开始已经提供了Base64的实现):

1
2
3
4
5
6
7
8
9
10
import org.junit.Test;
import java.util.Base64;

public class Base64Test {

@Test
public void demo1() {
System.out.println(Base64.getEncoder().encodeToString("hello".getBytes()));
}
}

程序输出也是aGVsbG8=

URL Base64算法

Base64编码值通过URL传输会出现问题,因为Base64编码中的“+”和“/”符号是不允许出现在URL中的。同样,符号“=”用做参数分隔符,也不允许出现在URL中,根据RFC 4648中的建议,“~”和“.”符都有可能替代“=”符号。但“~”符号与文件系统相冲突,不能使用;如果使用“.”符号,某些文件系统认为该符号连续出现两次则为错误。

所以common codec包下的URL Base64算法舍弃了填充符,使用了不定长URL Base64编码。

引入common codec依赖包:

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

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;

public class Base64Test {

@Test
public void demo1() {
String value = "hello";
System.out.println(Base64.encodeBase64String(value.getBytes()));
System.out.println(Base64.encodeBase64URLSafeString(value.getBytes()));
}

}

输出如下:

1
2
aGVsbG8=
aGVsbG8

请作者喝瓶肥宅水🥤

0