
三种编码,三种不同的任务
URL编码、Base64和十六进制都是将数据表示为可打印ASCII文本的方式——但它们解决的是完全不同的问题。用错编码是常见的错误来源:
- 直接在URL中传递Base64字符串时,如果包含
+和/会出错 - 对二进制数据进行URL编码而不是Base64编码,产生的字符串大小是原来的3倍
- 将密码哈希存储为Base64而不是十六进制是可以的,但在系统中混用会导致比较失败
以下是每种编码的适用场景。

URL编码(百分号编码)
目的: 使任意文本能够安全地包含在URL中。
工作原理: 每个不是URL安全字母数字或- _ . ~之一的字符都会被替换为%后跟其两位十六进制代码。
空格 → %20
& → %26
= → %3D
/ → %2F
+ → %2B
"hello world" → "hello%20world"
何时使用:
- 查询字符串参数:
?name=John+Doe或?name=John%20Doe - 包含特殊字符的路径段
- 表单数据提交(
application/x-www-form-urlencoded)
何时不应使用:
- 二进制数据(图片、文件)——输出会非常庞大
- 将存储在数据库中的数据——存储前应先解码
- 密码或密钥——它们根本不应出现在URL中
// JavaScript
encodeURIComponent('hello world & more') // "hello%20world%20%26%20more"
decodeURIComponent('hello%20world') // "hello world"
// encodeURI vs encodeURIComponent
encodeURI('https://example.com/path?q=hello world')
// "https://example.com/path?q=hello%20world" ← 保留 : // ? = &
encodeURIComponent('hello world')
// "hello%20world" ← 编码所有字符,包括 : / ? = &
from urllib.parse import quote, unquote, urlencode
quote('hello world & more') # 'hello%20world%20%26%20more'
quote('hello/world', safe='/') # 'hello/world' — / 不被编码
unquote('hello%20world') # 'hello world'
# 用于查询字符串
urlencode({'name': 'John Doe', 'age': 30})
# 'name=John+Doe&age=30' ← 查询字符串中空格编码为 +

Base64
目的: 将二进制数据表示为可打印的ASCII文本。
工作原理: 将3字节的二进制数据编码为4个ASCII字符,使用64个字符的字母表(A-Z a-z 0-9 + /)。输出大小始终是输入的4/3(增加33%)。
"Hello" → "SGVsbG8="
何时使用:
- 在JSON或XML中嵌入二进制文件(图片、PDF)
- 电子邮件附件(MIME编码)
- HTTP基本认证头(
Authorization: Basic dXNlcjpwYXNz) - 在文本字段中存储小型二进制数据块
- Data URL(
<img src="data:image/png;base64,iVBOR...">)
何时不应使用:
- URL——
+和/字符在Base64中是有效的,但在URL中有特殊含义。应使用Base64url(将+替换为-,/替换为_,并移除填充) - 大文件——33%的大小开销会累积
- 需要人类可读的数据
// 浏览器
btoa('Hello World') // "SGVsbG8gV29ybGQ="
atob('SGVsbG8gV29ybGQ=') // "Hello World"
// Node.js
Buffer.from('Hello World').toString('base64') // "SGVsbG8gV29ybGQ="
Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString() // "Hello World"
// Base64url(URL安全,无填充)
Buffer.from('Hello World').toString('base64url') // "SGVsbG8gV29ybGQ"
import base64
base64.b64encode(b'Hello World') # b'SGVsbG8gV29ybGQ='
base64.b64decode('SGVsbG8gV29ybGQ=') # b'Hello World'
# URL安全的Base64
base64.urlsafe_b64encode(b'Hello World') # b'SGVsbG8gV29ybGQ='

十六进制编码
目的: 将二进制数据表示为人类可读的十六进制数字字符串。
工作原理: 每个字节恰好变成两个十六进制数字(0–9, a–f)。一个32字节的SHA-256哈希变成64字符的十六进制字符串。
0x48 0x65 0x6c 0x6c 0x6f → "48656c6c6f"
何时使用:
- 加密哈希(SHA-256, MD5):
a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 - 需要可读的二进制标识符:交易ID、指纹
- 调试二进制数据
- 颜色代码:
#FF5733 - MAC地址:
00:1A:2B:3C:4D:5E
何时不应使用:
- 当空间效率重要时——十六进制是原始二进制大小的2倍(而Base64是1.33倍)
- 在JSON或HTTP中嵌入二进制——应使用Base64
// Node.js
crypto.createHash('sha256').update('hello').digest('hex')
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
Buffer.from('Hello').toString('hex') // "48656c6c6f"
Buffer.from('48656c6c6f', 'hex').toString() // "Hello"
import hashlib
hashlib.sha256(b'hello').hexdigest()
# "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
b'Hello'.hex() # '48656c6c6f'
bytes.fromhex('48656c6c6f') # b'Hello'
并排对比
| 属性 | URL编码 | Base64 | 十六进制 |
|---|---|---|---|
| 输入 | 文本字符串 | 任意二进制 | 任意二进制 |
| 输出大小 | 可变(最多3倍) | 输入的4/3 | 输入的2倍 |
| URL安全 | 是(这就是目的) | 否(使用Base64url) | 是 |
| 人类可读 | 一定程度上 | 否 | 一定程度上 |
| 标准用途 | 查询参数、表单数据 | 文本上下文中的二进制 | 哈希、指纹 |
| 填充 | 无 | = 填充 |
无 |
URL中的Base64陷阱
JWT令牌使用Base64url——而不是标准Base64。如果你用常规Base64解码JWT的头部或载荷,它可能能工作,因为特定字节恰好不包含+或/。但如果你的JWT库生成标准Base64并将其放入URL而不重新编码,任何+都会变成空格,/会创建路径段。
// URL中安全的JWT(base64url,无填充)
const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'; // 无 + / 或 =
// 危险:URL中的标准base64
const standard = Buffer.from(data).toString('base64');
// 可能包含 + / = — 必须为URL重新编码:
const urlSafe = standard.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');