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
关键差异
| 特性 | 标准Base64 | Base64URL |
|---|---|---|
| 第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"
兼容性注意事项
库支持
| 语言/库 | 标准Base64 | Base64URL |
|---|---|---|
| Python | base64.b64encode | base64.urlsafe_b64encode |
| JavaScript | btoa/atob | 需手动实现 |
| Java | Base64.getEncoder() | Base64.getUrlEncoder() |
| Go | base64.StdEncoding | base64.URLEncoding |
| PHP | base64_encode | 手动替换 |
| Ruby | Base64.encode64 | Base64.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语法