Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

Base64 是一种用 64 个可打印字符来表示二进制数据的方法。它之所以被创造出来,是因为很多网络传输协议(如电子邮件 SMTP)是设计用来传输文本的,对于二进制数据(如图片、文件等)中的控制字符(如NULL,换行符)处理得不好。Base64 将这些二进制数据“翻译”成纯文本字符,从而确保数据在传输过程中完整、不出错。

1. 核心原理

Base64 的核心思想是 将 3 个 8 位的字节(共 24 位)转换为 4 个 6 位的字节(也是 24 位),然后根据一个索引表,将这 4 个 6 位的数据映射为 4 个可打印的 ASCII 字符。

6位的数据可以表示64(2^6=64)个不同的字符,所以Base64编码后的数据长度会增加1/3。

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 /

填充字符是 =

2. 编码过程(Encode)

2.1. 正常Case

我们以字符串 "Man" 为例,它的 ASCII 码二进制表示是:

1
2
3
M = 77 (01001101)
a = 97 (01100001)
n = 110 (01101110)

连起来是:01001101 01100001 01101110

步骤 1: 按 6 位分组
将这 24 位数据按每 6 位一组分开:

1
010011 010110 000101 101110

步骤 2: 转换为十进制
将每组 6 位二进制数转换为十进制:

1
2
3
4
010011 -> 19
010110 -> 22
000101 -> 5
101110 -> 46

步骤 3: 查表映射
根据 Base64 索引表,找到对应的字符:

1
2
3
4
19 -> T
22 -> W
5 -> F
46 -> u

所以,"Man" 的 Base64 编码结果是 "TWFu"

2.2. 处理字节数不是3的倍数的情况

如果原始数据的字节数不是 3 的倍数,编码过程会稍有不同,并在末尾使用 = 进行填充。

如两个字节 “Ma”:

1
2
M = 77 (01001101)
a = 97 (01100001)

连起来是:01001101 01100001,一共 16 位。

步骤 1: 按 6 位分组(并在末尾补 0):

16 位不是 6 的倍数,我们在末尾补两个 0,凑成 18 位(3 个 6 位组):
010011 010110 000100 (注意最后 4 位 0001 变成了 000100)

步骤 2: 转换为十进制:

1
2
3
010011 -> 19
010110 -> 22
000100 -> 4

步骤 3: 查表映射:

1
2
3
19 -> T
22 -> W
4 -> E

步骤 4: 填充:

因为我们原始数据只有 2 个字节(本应处理 3 个字节),所以需要在编码结果后加 一个 =
最终结果:"TWE="

3. 解码过程(Decode)

解码是编码的逆过程。

  1. 去掉末尾的填充符 =(如果有的话)。
  2. 将每个 Base64 字符根据索引表转换回对应的 6 位二进制值。
  3. 将所有 6 位二进制值连接起来。
  4. 每 8 位一组,转换回原始的字节数据。在编码时填充的 0 位会在这一步被自然地忽略掉。

"TWE=" 为例:

  1. 去掉 =,剩下 "TWE"
  2. 查表:
    • T -> 19 -> 010011
    • W -> 22 -> 010110
    • E -> 4 -> 000100
  3. 连接起来:010011 010110 000100 -> 01001101 01100001 000100??
    (最后 4 位 0100 是补的 0,实际有效数据是前面的 01,加上前面的 01100001,正好是 a 的 ASCII 码)
  4. 按 8 位分组:
    • 01001101 -> 77 -> ‘M’
    • 01100001 -> 97 -> ‘a’
      (最后一个字节 000100?? 因为原始数据只有 2 个字节,所以被丢弃)
      解码结果为 "Ma"

4. 在编程中的使用

几乎所有现代编程语言都内置了 Base64 编解码的支持。

4.1. Python

1
2
3
4
5
6
7
8
9
10
11
12
import base64

# 编码
original_string = "Hello, World!"
encoded_bytes = base64.b64encode(original_string.encode('utf-8'))
encoded_string = encoded_bytes.decode('utf-8')
print(encoded_string) # SGVsbG8sIFdvcmxkIQ==

# 解码
decoded_bytes = base64.b64decode(encoded_string)
decoded_string = decoded_bytes.decode('utf-8')
print(decoded_string) # Hello, World!

4.2. JavaScript

1
2
3
4
5
6
7
8
// 编码
let originalString = "Hello, World!";
let encodedString = btoa(originalString); // "Base64 Encode"
console.log(encodedString); // SGVsbG8sIFdvcmxkIQ==

// 解码
let decodedString = atob(encodedString); // "ASCII to Binary"
console.log(decodedString); // Hello, World!

4.3. Java

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

// 编码
String originalString = "Hello, World!";
String encodedString = Base64.getEncoder().encodeToString(originalString.getBytes());
System.out.println(encodedString); // SGVsbG8sIFdvcmxkIQ==

// 解码
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
String decodedString = new String(decodedBytes);
System.out.println(decodedString); // Hello, World!

5. 总结

  • 目的:将二进制数据安全地转换为纯文本,用于不支持二进制数据的传输环境。
  • 原理:将每 3 个字节(24 位)的数据重新划分为 4 个 6 位的单元,每个单元映射到一个 Base64 索引表中的可打印字符。
  • 填充:如果原始数据字节数不是 3 的倍数,在编码结果末尾用 = 填充。
  • 应用:电子邮件附件、在 HTML 中嵌入图片(Data URLs)、在 XML/JSON 中传输二进制数据等。

评论