Skip to main content

5. Base 64 Encoding with URL and Filename Safe Alphabet

URL和文件名安全的Base64编码

[12] 中使用了URL和文件名安全字母表的Base64编码。

曾有人建议使用""作为第63个字符的替代字母表。由于""字符在某些文件系统环境中具有特殊含义,因此推荐使用本节中描述的编码。剩余的未保留URI字符是".",但某些文件系统环境不允许文件名中有多个".",因此"."字符也不太适合。

填充字符"="在URI [9] 中使用时通常会被百分号编码,但如果隐式知道数据长度,则可以通过跳过填充来避免这种情况;见第3.2节。

此编码可称为"base64url"。 此编码不应被视为与"base64"编码相同,也不应仅称为"base64"。除非另有明确说明,"base64"指的是上一节中的base64。


与标准Base64的区别

此编码在技术上与前一个编码相同,除了第62和第63个字母表字符,如表2所示。

表2: "URL和文件名安全"的Base64字母表

值  编码  值  编码  值  编码  值  编码
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 - (减号)
12 M 29 d 46 u 63 _ (下划线)
13 N 30 e 47 v
14 O 31 f 48 w 填充 =
15 P 32 g 49 x
16 Q 33 h 50 y

关键差异

特性标准Base64Base64URL
第62字符+ (加号)- (减号/连字符)
第63字符/ (斜杠)_ (下划线)
填充必需通常省略
用途通用编码URL、文件名

为什么需要URL安全变体?

标准Base64的问题

问题1: URL参数中的特殊字符
标准Base64: "hello+world/test="
URL中: "?data=hello+world/test="
↑ ↑
被解释为空格 路径分隔符

问题2: 文件名中的非法字符
标准Base64: "file+name/data="
Windows: ❌ "/" 是路径分隔符
Unix: ❌ "/" 是路径分隔符

问题3: 百分号编码开销
标准Base64: "abc+def/ghi="
URL编码后: "abc%2Bdef%2Fghi%3D"
(长度增加)

Base64URL的解决方案

Base64URL:  "hello-world_test"
URL中: "?data=hello-world_test"
✅ 无需编码,直接使用

文件名: "hello-world_test"
✅ 所有系统都支持

填充处理

标准做法(带填充)

输入: "hello"
标准Base64: "aGVsbG8="
Base64URL: "aGVsbG8=" (保留填充)

无填充模式(推荐)

输入: "hello"
Base64URL (无填充): "aGVsbG8" (省略 "=")

优点:
✅ 更短
✅ 无需URL编码 "="
✅ 更清晰的URL

填充可选的原因

如果知道原始数据长度,填充是冗余的:

编码长度推导原始长度:

编码长度 可能的原始长度
2字符 → 1字节
3字符 → 2字节
4字符 → 3字节
5字符 → 4字节
...

规则: 原始字节数 = ⌊编码长度 × 3 / 4⌋

实际应用场景

JWT (JSON Web Token)

JWT结构: header.payload.signature

示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

注意:
- 使用Base64URL编码
- 省略填充符号
- 可以直接放在URL中

OAuth 2.0 令牌

授权码:
code=SplxlOBeZQQYbYS6WxSbIA

Base64URL编码(无填充)

刷新令牌:
refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

Base64URL编码

URL参数

图片数据URI:
标准Base64 (需要编码):
?img=iVBORw0KGgo%3D...
↑ "=" 被编码为 %3D

Base64URL (无需编码):
?img=iVBORw0KGgo...
✅ 直接使用

文件名

生成唯一文件名:
标准Base64: "image+data/2024=" ❌ 包含 "/" 和 "="
Base64URL: "image-data_2024" ✅ 安全的文件名

示例:
session-token_abc123xyz.json
user-avatar_def456uvw.png

编码实现

Python实现

import base64

def base64url_encode(data):
"""Base64URL编码(无填充)"""
# 标准Base64编码
encoded = base64.b64encode(data)

# 替换字符
encoded = encoded.replace(b'+', b'-')
encoded = encoded.replace(b'/', b'_')

# 移除填充
encoded = encoded.rstrip(b'=')

return encoded.decode('ascii')

def base64url_decode(encoded):
"""Base64URL解码"""
# 添加填充(如果需要)
padding = 4 - (len(encoded) % 4)
if padding != 4:
encoded += '=' * padding

# 替换回标准Base64字符
encoded = encoded.replace('-', '+')
encoded = encoded.replace('_', '/')

return base64.b64decode(encoded)

JavaScript实现

function base64urlEncode(str) {
// 标准Base64编码
let base64 = btoa(str);

// 替换字符并移除填充
return base64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

function base64urlDecode(base64url) {
// 替换回标准Base64字符
let base64 = base64url
.replace(/-/g, '+')
.replace(/_/g, '/');

// 添加填充
const padding = 4 - (base64.length % 4);
if (padding !== 4) {
base64 += '='.repeat(padding);
}

return atob(base64);
}

Go实现

import (
"encoding/base64"
"strings"
)

func Base64URLEncode(data []byte) string {
// 使用Go标准库的RawURLEncoding(无填充)
return base64.RawURLEncoding.EncodeToString(data)
}

func Base64URLDecode(encoded string) ([]byte, error) {
return base64.RawURLEncoding.DecodeString(encoded)
}

转换对照表

标准Base64 → Base64URL

标准Base64           Base64URL
------------ -----------
abc+def abc-def
xyz/123 xyz_123
test= test (或 test=)
hello== hello (或 hello==)
world+test/data= world-test_data

示例转换

原始数据: "Hello, World!"

标准Base64:
"SGVsbG8sIFdvcmxkIQ=="
↓ 替换 + → -
↓ 替换 / → _
↓ 移除 =
Base64URL:
"SGVsbG8sIFdvcmxkIQ"

兼容性注意事项

库支持

语言/库标准Base64Base64URL
Pythonbase64.b64encodebase64.urlsafe_b64encode
JavaScriptbtoa/atob需手动实现
JavaBase64.getEncoder()Base64.getUrlEncoder()
Gobase64.StdEncodingbase64.URLEncoding
PHPbase64_encode手动替换
RubyBase64.encode64Base64.urlsafe_encode64

互操作性

发送方和接收方必须约定:
1. 使用标准Base64还是Base64URL
2. 是否包含填充
3. 如何处理换行符

推荐:
- URL/文件名场景使用Base64URL
- 明确标注编码类型
- 文档化填充策略

安全考虑

URL注入

错误做法:
url = "https://example.com/api?token=" + user_input
(user_input可能包含 "&" 等特殊字符)

正确做法:
token = base64url_encode(user_input)
url = "https://example.com/api?token=" + token

文件名注入

错误做法:
filename = user_input + ".txt"
(user_input可能包含 "../" 等路径遍历)

正确做法:
safe_name = base64url_encode(user_input)
filename = safe_name + ".txt"

长度限制

注意:
- URL长度限制 (通常2048字符)
- 文件名长度限制 (通常255字符)

Base64URL编码会增加约33%的长度

最佳实践

✅ 推荐做法

1. URL参数
使用Base64URL,省略填充

2. JWT
使用Base64URL,省略填充

3. 文件名
使用Base64URL,省略填充

4. Cookie值
使用Base64URL,保留或省略填充(约定一致)

5. API令牌
使用Base64URL,省略填充

❌ 避免做法

1. 混用标准Base64和Base64URL
2. 不一致的填充策略
3. 忘记文档化编码类型
4. 假设所有Base64都是标准格式
5. 不验证解码后的数据

相关规范

  • RFC 4648 §4 - 标准Base64
  • RFC 7515 - JSON Web Signature (JWS)
  • RFC 7519 - JSON Web Token (JWT)
  • RFC 6749 - OAuth 2.0
  • RFC 3986 - URI语法