Base64 编码详解:何时以及如何使用

· 12分钟阅读

目录

什么是 Base64 编码?

Base64 是一种二进制到文本的编码方案,它将二进制数据转换为 ASCII 字符串。它仅使用 64 个可打印字符来表示二进制数据:A-Za-z0-9+/,并使用 = 进行填充。

这种编码使得通过纯文本通道(如电子邮件、JSON API、XML 文档和 HTML)安全传输二进制数据成为可能。如果没有 Base64,二进制数据可能会被期望文本的系统损坏或误解。

"Base64" 这个名称来源于编码中使用的 64 字符字母表。与加密或哈希不同,Base64 不是一种安全措施——它只是一种将二进制数据表示为文本的方式。任何人都可以在没有任何密钥或密码的情况下将 Base64 解码回其原始形式。

Base64 在现代 Web 开发中无处不在:

了解何时以及如何使用 Base64 是任何从事 Web API、数据传输或文件处理工作的开发人员的基本技能。

Base64 的底层工作原理

编码过程将每 3 个字节(24 位)的输入转换为 4 个 Base64 字符(每个 6 位)。这是定义 Base64 的核心数学关系。

以下是一个具体示例的分步过程:

# 步骤 1: 取 3 字节输入
输入文本: "Hi!"
二进制: 01001000 01101001 00100001

# 步骤 2: 分割为 6 位组
010010 000110 100100 100001

# 步骤 3: 将每个 6 位组映射到 Base64 字母表
010010 → S (18)
000110 → G (6)
100100 → k (36)
100001 → h (33)

# 结果: "Hi!" → "SGkh"

Base64 字母表将每个 6 位值(0-63)映射到特定字符:

值范围 字符 描述
0-25 A-Z 大写字母
26-51 a-z 小写字母
52-61 0-9 数字
62 + 加号
63 / 正斜杠
- = 填充字符

理解填充

当输入长度不能被 3 整除时,Base64 会添加 = 字符进行填充,使输出长度能被 4 整除。这确保了正确的解码。

# 填充示例
"A"    → "QQ=="   (1 字节 → 2 字符 + ==)
"AB"   → "QUI="   (2 字节 → 3 字符 + =)
"ABC"  → "QUJD"   (3 字节 → 4 字符,无填充)

填充告诉解码器原始输入中有多少字节。没有它,解码器将不知道最后一组应该期望 1、2 还是 3 个字节。

专业提示: Base64 总是将数据大小增加约 33%。每 3 个字节变成 4 个字符,因此 300KB 的图像在 Base64 编码后会变成大约 400KB。

为什么每个字符 6 位?

选择每个字符 6 位是经过深思熟虑的。使用 6 位,您可以表示 2^6 = 64 个不同的值,这完美映射到 64 字符字母表。

这也意味着 3 字节(24 位)可以均匀地分成四个 6 位块,创建了一个高效的编码方案,开销最小。

实践中的编码和解码

大多数编程语言都提供用于 Base64 编码和解码的内置函数或标准库。以下是流行语言的实际示例:

JavaScript / Node.js

// 浏览器(现代)
const text = "Hello, World!";
const encoded = btoa(text);
console.log(encoded); // SGVsbG8sIFdvcmxkIQ==

const decoded = atob(encoded);
console.log(decoded); // Hello, World!

// Node.js
const buffer = Buffer.from("Hello, World!");
const encoded = buffer.toString('base64');
console.log(encoded); // SGVsbG8sIFdvcmxkIQ==

const decoded = Buffer.from(encoded, 'base64').toString();
console.log(decoded); // Hello, World!

Python

import base64

# 编码
text = "Hello, World!"
encoded = base64.b64encode(text.encode())
print(encoded)  # b'SGVsbG8sIFdvcmxkIQ=='

# 解码
decoded = base64.b64decode(encoded).decode()
print(decoded)  # Hello, World!

Java

import java.util.Base64;

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

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

命令行

# 编码
echo -n "Hello, World!" | base64
# SGVsbG8sIFdvcmxkIQ==

# 解码
echo "SGVsbG8sIFdvcmxkIQ==" | base64 -d
# Hello, World!

# 编码文件
base64 image.png > image.txt

# 解码文件
base64 -d image.txt > image.png

对于快速编码和解码任务,您可以直接在浏览器中使用我们的 Base64 编码器 工具,无需编写任何代码。

快速提示: 编码二进制文件时,始终使用原始字节,而不是文本表示。先将二进制转换为文本会损坏数据。

Base64 的常见用例

Base64 编码解决了数据传输和存储中的特定问题。以下是您会遇到或需要使用 Base64 的最常见场景:

1. 嵌入图像的数据 URI

数据 URI 允许您直接在 HTML 或 CSS 中嵌入图像,而无需单独的 HTTP 请求。这对于小图标、徽标或内联 SVG 特别有用。

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." alt="Red dot" />

/* CSS */
.icon {
  background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...);
}

好处包括减少 HTTP 请求和保证可用性(没有损坏的图像链接)。但是,这会增加 HTML/CSS 文件大小,并阻止浏览器单独缓存图像。

2. 电子邮件附件(MIME)

电子邮件协议是为 7 位 ASCII 文本设计的。Base64 编码允许通过电子邮件系统安全传输 PDF、图像和文档等二进制附件。

MIME(多用途互联网邮件扩展)标准使用 Base64 对电子邮件中的附件进行编码。这由电子邮件客户端和服务器自动处理。

3. API 身份验证

HTTP 基本身份验证在 Authorization 标头中将凭据编码为 Base64:

// 用户名: user, 密码: pass
// 组合: user:pass
// Base64: dXNlcjpwYXNz

Authorization: Basic dXNlcjpwYXNz

请记住:Base64 不是加密。通过 HTTP 的基本认证是不安全的。传输凭据时始终使用 HTTPS。

4. JSON Web Tokens (JWT)

JWT 对标头和有效负载部分使用 Base64URL 编码(一种 URL 安全变体):

// JWT 结构: header.payload.signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

每个部分都是 Base64URL 编码的 JSON,使 JWT 可读并可在 URL 和 HTTP 标头中传输。

5. 在数据库中存储二进制数据

某些数据库或数据格式(如 JSON 或 XML)不能很好地处理二进制数据。Base64 编码允许您将二进制数据存储为文本:

{
  "user_id": 12345,
  "profile_image": "iVBORw0KGgoAAAANSUhEUgAAAAUA...",
  "document": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8..."
}

这在需要包含二进制数据的 NoSQL 数据库、配置文件和 API 响应中很常见。

6. 证书和密钥文件

PEM(隐私增强邮件)格式使用 Base64 对证书、私钥和公钥进行编码:

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRKmzMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
...
-----END CERTIFICATE-----

这使得证书和密钥可以在不同系统之间移植,并且易于复制粘贴。

API 和 Web 开发中的 Base64

Web API 经常出于各种目的使用 Base64。理解这些模式可以帮助您有效地使用第三方 API 并设计自己的 API。

文件上传 API

许多 API 接受 JSON 有效负载中的 Base64 编码字符串形式的文件上传:

POST /api/upload
Content-Type: application/json

{
  "filename": "document.pdf",
  "content": "JVBERi0xLjQKJeLjz9MK...",
  "encoding": "base64"
}

这种方法适用于小文件,但对于大型上传来说效率低下。对于超过 1-2MB 的文件,请考虑使用 multipart/form-data 或直接二进制上传。

Webhook 有效负载

Webhook 通常包含 Base64 编码的数据,以确保二进制内容在 JSON 序列化中保持完整:

{
  "event": "document.signed",
  "document_id": "doc_123",
  "signed_pdf": "JVBERi0xLjQKJeLjz9MK...",
  "timestamp": "2026-03-31T10:30:00Z"
}

GraphQL 和 Base64

GraphQL 通常使用 Base64 进行基于游标的分页和编码复杂 ID:

query {
  users(first: 10, after: "Y3Vyc29yOjEwMA==") {
    edges {
      cursor
      node {
        id  # 通常是 Base64 编码的: "VXNlcjoxMjM="
        name
      }
    }
  }
}

Relay 规范推广了这种模式,使用 Base64 对游标位置和全局 ID 进行编码。

专业提示: 设计 API 时,记录您使用的是标准 Base64 还是 Base64URL 编码。这种差异对 URL 参数很重要,可能会导致细微的错误。

浏览器 API 和 Base64

现代浏览器 API 经常使用 Base64:

// Canvas 转 Base64
const canvas = document.getElementById('myCanvas');
const dataURL = canvas.toDataURL('image/png');
// data:image/png;base64,iVBORw0KGgo...

// 文件转 Base64
const file = document.getElementById('fileInput').files[0];
const reader = new FileReader();
reader.onload = (e) => {
  const base64 = e.target.result.split(',')[1];
  console.log(base64);
};
reader.readAsDataURL(file);

// 使用 Base64 认证的 Fetch
fetch('/api/data', {
  headers: {
    'Authorization': 'Basic ' + btoa('username:password')
  }
});

性能考虑和开销

Base64 编码带来的权衡会影响性能、带宽和存储。理解这些有助于您就何时使用它做出明智的决定。

大小开销

Base64 将数据大小增加约 33%。以下是计算方法:

原始大小 Base64 大小 开销
10 KB 13.3 KB +3.3 KB
100 KB 133 KB +33 KB
1 MB 1.33 MB +333 KB
10 MB 13.3 MB +3.3 MB
100 MB 133 MB +33 MB

这种开销对于带宽受限的环境、移动应用程序和大文件传输很重要。

处理速度

编码和解码 Base64 需要 CPU 周期。对于少量数据,这可以忽略不计。对于大文件或高吞吐量系统,它可能成为瓶颈。

现代实现经过高度优化,但如果性能至关重要,您仍应进行基准测试:

// JavaScript 基准测试示例
const data = new Uint8Array(1024 * 1024); // 1MB
crypto.getRandomValues(data);

console.time('encode');
const encoded = btoa(String.fromCharCode(...data));
console.timeEnd('encode');

console.time('decode');
const decoded = atob(encoded);
console.timeEnd('decode');

内存使用

Base64 操作通常需要同时在内存中保存原始数据和编码数据。对于大文件,这可能会使编码/解码期间的内存需求翻倍。

对于大文件,请考虑流式方法:

// Node.js 流式示例
const fs = require('fs');
const { Transform } = require('stream');

fs.createReadStream('large-file.bin')
  .pipe(new Transform({
    transform(chunk, encoding, callback) {
      callback(null, chunk.toString('base64'));
    }
  }))
  .pipe(fs.createWriteStream('large-file.b64'));
We use cookies for analytics. By continuing, you agree to our Privacy Policy.