Base64 编码详解:工作原理、Data URI 与 JWT
学习 Base64 编码的工作原理、存在意义及应用场景。涵盖编码算法、Data URI、JWT 令牌、邮件附件及 7 种编程语言的代码示例。
Base64 编码详解:工作原理、Data URI 与 JWT
Base64 编码是计算机领域使用最广泛的编码方案之一,然而许多开发者在使用它时并不真正理解其原理或存在意义。每当你在 HTML 邮件中嵌入图片、解码 JWT 令牌、处理 HTTP 基本认证,或打开 PEM 证书文件时,都在与 Base64 打交道。
本文从零开始讲解 Base64 编码算法,探讨其最常见的应用场景,并展示如何在七种编程语言中对 Base64 进行编码和解码。你也可以试用我们免费的 Base64 编码解码工具,对以下任意示例进行实操验证。
什么是 Base64?
Base64 是一种二进制转文本的编码方案,使用 64 个可打印的 ASCII 字符来表示二进制数据。它的设计初衷是在只支持文本的信道(如电子邮件 SMTP 协议和早期 HTTP)上安全传输二进制数据。
"Base64"这个名称来源于它所使用的 64 字符字母表:
| 索引范围 | 字符 | 数量 |
|---|---|---|
| 0–25 | A–Z | 26 |
| 26–51 | a–z | 26 |
| 52–61 | 0–9 | 10 |
| 62 | + | 1 |
| 63 | / | 1 |
| 填充 | = | — |
字符总数恰好为 64(即 2 的 6 次方),因此每个 Base64 字符精确编码 6 位数据。
Base64 编码的工作原理
Base64 算法通过三个步骤将二进制数据进行转换:
第一步:将输入转换为二进制
每个输入字节用 8 位表示。对于文本输入,需先将每个字符转换为其 ASCII 或 UTF-8 字节值。
示例:文本"Man"的 ASCII 表示:
| 字符 | ASCII 十进制值 | 二进制(8 位) |
|---|---|---|
| M | 77 | 01001101 |
| a | 97 | 01100001 |
| n | 110 | 01101110 |
第二步:拆分为 6 位分组
将合并后的 24 位(3 字节 × 8 位)拆分为四组,每组 6 位:
原始: 01001101 01100001 01101110
-------- -------- --------
重组后: 010011 010110 000101 101110
------ ------ ------ ------
第三步:映射到 Base64 字符
每个 6 位值(0–63)映射到 Base64 字母表中的对应字符:
| 6 位值 | 十进制 | Base64 字符 |
|---|---|---|
| 010011 | 19 | T |
| 010110 | 22 | W |
| 000101 | 5 | F |
| 101110 | 46 | u |
结果:"Man" → "TWFu"
处理填充
由于 Base64 以 3 字节为一组(24 位 = 四个 6 位分组),当输入长度不是 3 字节的整数倍时,需要添加填充:
| 输入长度 | 所需填充 | 示例 |
|---|---|---|
| 3 字节(可整除) | 无 | "Man" → "TWFu" |
| 2 字节(余 2) | 一个 = | "Ma" → "TWE=" |
| 1 字节(余 1) | 两个 == | "M" → "TQ==" |
= 填充字符告知解码器最后一组实际编码了多少字节。
33% 的体积增大
Base64 编码会使数据体积增大约 33%。这是因为每 3 字节输入(24 位)会变为 4 字节输出(4 个 ASCII 字符),精确比例为 4/3 = 1.333……
| 原始大小 | Base64 大小 | 额外开销 |
|---|---|---|
| 1 KB | 1.33 KB | +0.33 KB |
| 10 KB | 13.3 KB | +3.3 KB |
| 100 KB | 133 KB | +33 KB |
| 1 MB | 1.33 MB | +0.33 MB |
这一开销是 Base64 的根本权衡:以更大的体积换取安全的文本表示。因此,Base64 最适合小型数据,除非必要,不建议用于编码大文件。
Base64 的应用场景
1. Data URI(在 HTML/CSS 中嵌入文件)
Data URI 允许你将文件内容直接嵌入 HTML 或 CSS,从而省去一次 HTTP 请求:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." alt="小图标" />
格式为:data:[MIME 类型];base64,[编码数据]
适合使用 Data URI 的场景:
- 5–10 KB 以下的小图片(图标、装饰性元素)
- 每个页面都会用到的 CSS 背景图
- 不需要单独缓存的一次性图片
不适合使用 Data URI 的场景:
- 大于 10 KB 的图片(33% 的额外开销会迅速累积)
- 在多个页面间共享的图片(无法单独缓存)
- 性能敏感的页面(Base64 字符串会增加 HTML 的解析时间)
2. JWT(JSON Web 令牌)
JWT 令牌使用 Base64 的 URL 安全变体(称为 Base64URL)来编码其三个部分:
头部.载荷.签名
每个部分均经过 Base64URL 编码。例如,解码一个 JWT 载荷:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
解码后得到:
{"sub":"1234567890","name":"John Doe","iat":1516239022}
重要提示:JWT 令牌是编码的,并非加密的。任何人都可以解码并读取其载荷。切勿在 JWT 载荷中存储敏感数据(如密码、信用卡号)。
3. 邮件附件(MIME)
电子邮件最初是为纯 ASCII 文本设计的。MIME(多用途互联网邮件扩展)标准使用 Base64 对二进制附件进行编码:
Content-Type: application/pdf
Content-Transfer-Encoding: base64
JVBERi0xLjQKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwov...
你曾经发送或接收过的每一个邮件附件,在传输过程中都经过了 Base64 编码。
4. HTTP 基本认证
HTTP 基本认证将 用户名:密码 凭据以 Base64 格式编码后传输:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
解码 dXNlcm5hbWU6cGFzc3dvcmQ= 得到的是 username:password。这种方式本身并不安全——必须配合 HTTPS 一起使用。
5. 密钥与证书
PEM(隐私增强邮件)格式在头尾行之间包裹经过 Base64 编码的二进制数据:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
SSL/TLS 证书、SSH 密钥和 GPG 密钥都采用这种格式。
标准 Base64 与 URL 安全 Base64
标准 Base64 使用 + 和 /,而这两个字符在 URL 中有特殊含义。URL 安全 Base64(即 Base64URL,定义于 RFC 4648)对其进行了替换:
| 特性 | 标准 Base64 | URL 安全 Base64 |
|---|---|---|
| 第 62 号字符 | + | - |
| 第 63 号字符 | / | _ |
| 填充 | =(必须) | 通常省略 |
| 使用场景 | 邮件、PEM、Data URI | URL、JWT、文件名 |
Base64 与其他编码方案的对比
| 编码方案 | 进制 | 体积增大 | 字符集 | 常见用途 |
|---|---|---|---|---|
| Base64 | 64 | 约 33% | A–Z、a–z、0–9、+、/ | 邮件、Data URI、JWT |
| Base32 | 32 | 约 60% | A–Z、2–7 | TOTP 验证码、.onion 地址 |
| Base16(十六进制) | 16 | 100% | 0–9、A–F | 哈希摘要、颜色代码 |
| Base85(Ascii85) | 85 | 约 25% | 33–117 ASCII | PDF、Git 二进制补丁 |
在大多数使用场景中,Base64 在紧凑性与兼容性之间取得了最佳平衡。
各编程语言中的 Base64
JavaScript
// 编码(仅支持 ASCII)
const encoded = btoa('Hello World'); // "SGVsbG8gV29ybGQ="
// 解码
const decoded = atob('SGVsbG8gV29ybGQ='); // "Hello World"
// 支持 Unicode 的编码
const unicodeEncode = btoa(unescape(encodeURIComponent('Hello 世界')));
// 支持 Unicode 的解码
const unicodeDecode = decodeURIComponent(escape(atob(encoded)));
Python
import base64
# 编码
encoded = base64.b64encode(b'Hello World').decode('utf-8') # "SGVsbG8gV29ybGQ="
# 解码
decoded = base64.b64decode('SGVsbG8gV29ybGQ=').decode('utf-8') # "Hello World"
# URL 安全变体
url_safe = base64.urlsafe_b64encode(b'Hello World').decode('utf-8')
命令行(macOS/Linux)
# 编码
echo -n 'Hello World' | base64 # SGVsbG8gV29ybGQ=
# 解码
echo 'SGVsbG8gV29ybGQ=' | base64 --decode # Hello World
# 编码文件
base64 image.png > image.b64
# 解码文件
base64 --decode image.b64 > image.png
Java
import java.util.Base64;
// 编码
String encoded = Base64.getEncoder().encodeToString("Hello World".getBytes());
// 解码
byte[] decoded = Base64.getDecoder().decode("SGVsbG8gV29ybGQ=");
String text = new String(decoded); // "Hello World"
PHP
// 编码
$encoded = base64_encode('Hello World'); // "SGVsbG8gV29ybGQ="
// 解码
$decoded = base64_decode('SGVsbG8gV29ybGQ='); // "Hello World"
Go
import "encoding/base64"
// 编码
encoded := base64.StdEncoding.EncodeToString([]byte("Hello World"))
// 解码
decoded, _ := base64.StdEncoding.DecodeString("SGVsbG8gV29ybGQ=")
C#
// 编码
string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes("Hello World"));
// 解码
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String("SGVsbG8gV29ybGQ="));
Base64 不是加密
一个常见的误解:Base64 经常被与加密混淆,但两者有着本质区别:
| 方面 | Base64 编码 | 加密 |
|---|---|---|
| 目的 | 数据格式转换 | 数据保护 |
| 可逆性 | 任何人都可解码 | 需要密钥 |
| 安全性 | 无 | 强(正确实现时) |
| 使用场景 | 传输兼容性 | 机密性保护 |
切勿使用 Base64 来「隐藏」敏感数据。 它不提供任何安全保障。如果需要保护数据,请使用正规的加密算法(如 AES-256、RSA),然后可选地对加密后的输出进行 Base64 编码以便传输。
常见问题
可以对图片进行 Base64 编码吗?
可以。将图片文件以二进制字节方式读取,然后对字节进行 Base64 编码,得到的文本字符串可嵌入 HTML、CSS、JSON 或任何文本格式。但编码后的字符串会比原始文件大约 33%。用于网页时,建议只将小图片(5–10 KB 以下)作为 Data URI 嵌入。
为什么我的 Base64 字符串以 = 或 == 结尾?
= 字符是填充符。当输入长度不是 3 字节的整数倍时就会出现。一个 = 表示最后一组有 2 个字节;== 表示最后一组只有 1 个字节。填充确保编码后的字符串长度始终是 4 的整数倍。
各编程语言中的 Base64 编码结果一致吗?
标准 Base64 算法(RFC 4648)在所有语言中完全一致——相同的输入始终产生相同的输出。不过,某些语言的默认行为在换行处理上有所不同(例如 Java 的 getMimeEncoder() 会每隔 76 个字符插入换行,以符合 MIME 规范)。
准备好编码或解码 Base64 了吗?试用我们的 Base64 编码解码工具,可即时转换并对比体积变化。如需其他编码工具,请查看我们的 URL 编码解码器、十六进制转文本工具和二进制转换器。