正在加载,请稍候…

URL编码 vs Base64 vs Hex:何时使用哪种编码

URL编码(百分号编码)、Base64和十六进制编码的实用对比——各自的作用、适用场景以及导致数据损坏的常见错误

URL编码 vs Base64 vs Hex:何时使用哪种编码

三种编码,三种不同的任务

URL编码、Base64和十六进制都是将数据表示为可打印ASCII文本的方式——但它们解决的是完全不同的问题。用错编码是常见的错误来源:

  • 直接在URL中传递Base64字符串时,如果包含+/会出错
  • 对二进制数据进行URL编码而不是Base64编码,产生的字符串大小是原来的3倍
  • 将密码哈希存储为Base64而不是十六进制是可以的,但在系统中混用会导致比较失败

以下是每种编码的适用场景。

URL编码 vs Base64 vs Hex:何时使用哪种编码 插图

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'  ← 查询字符串中空格编码为 +

URL编码 vs Base64 vs Hex:何时使用哪种编码 插图

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='

URL编码 vs Base64 vs Hex:何时使用哪种编码 插图

十六进制编码

目的: 将二进制数据表示为人类可读的十六进制数字字符串。

工作原理: 每个字节恰好变成两个十六进制数字(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, '');

→ 使用URL编码器编码和解码URL,或使用Base64转换器编码Base64。