Python str.translate + Base64 = 常见的 URL-Safe 变体或混淆

看到 Python 代码里:

b64 = base64.b64encode(data.encode("utf-8")).decode("ascii")
return b64.translate(TRANS)

这里的 .translate() 不是 Base64 库的方法——是 Python 字符串内置的 str.translate()。整个逻辑其实两步:

  1. b64encode 把数据编成标准 Base64 字符串
  2. translate(TRANS) 按映射表把字符逐个替换

常见用途:URL-Safe Base64简单混淆自定义字母表

str.translate 是什么

str.translate(table) 按照 table 里的映射一次性替换多个字符——比一堆 .replace() 快得多:

TRANS = str.maketrans({"a": "x", "b": "y"})
"abc".translate(TRANS)   # "xyc"

maketrans 三种参数形式

# 字典:字符 → 字符
str.maketrans({"a": "x", "b": "y"})

# 两个字符串:一一对应
str.maketrans("abc", "xyz")   # a→x, b→y, c→z

# 三个参数:第三个是"要删除的字符"
str.maketrans("", "", "aeiou")   # 删除所有元音
"hello world".translate(...)     # "hll wrld"

底层是 dict[int, int|None]translate 每个字符按 code point 查表。

URL-Safe Base64(最常见用途)

标准 Base64 用了 +/,在 URL 里出现要 percent-encode(%2B %2F)。URL-safe 变体换掉这两个字符:

TRANS = str.maketrans({"+": "-", "/": "_"})

b64 = base64.b64encode(data).decode("ascii")
url_safe = b64.translate(TRANS)

其实 Python 标准库直接提供了:

base64.urlsafe_b64encode(data).decode("ascii")

自己写 translate 版本效果一样,一般是兼容其它平台的历史代码

反向:URL-Safe → 标准

TRANS_BACK = str.maketrans({"-": "+", "_": "/"})

standard_b64 = url_safe.translate(TRANS_BACK)

# 补 padding(长度必须是 4 的倍数)
padded = standard_b64 + "=" * (-len(standard_b64) % 4)

data = base64.b64decode(padded)

URL-safe Base64 通常省掉了 = padding——解码时得手动补。

完全自定义字母表

某些爬虫 / 反调试代码会用非标准字母表做混淆:

CUSTOM_ALPHABET = "NOPQRSTUVWXYZABCDEFGHIJKLM" + \
                  "nopqrstuvwxyzabcdefghijklm" + \
                  "0123456789-_"

STANDARD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + \
                    "abcdefghijklmnopqrstuvwxyz" + \
                    "0123456789+/"

TRANS = str.maketrans(STANDARD_ALPHABET, CUSTOM_ALPHABET)

编码时把标准 Base64 输出通过 TRANS 转成”看起来不一样但结构相同”的字符串。解码时反向映射再走标准 b64decode。

这是很低强度的混淆——攻击者拿到映射表就完全还原了。但作为”不让日志里的密码一眼可读”够用。

性能对比

translate 一次映射多个字符,比 .replace() 快很多:

import timeit

s = "a" * 10000

t1 = timeit.timeit(
    "s.replace('a', 'x').replace('b', 'y')",
    globals={"s": s},
    number=10000,
)

TRANS = str.maketrans({"a": "x", "b": "y"})
t2 = timeit.timeit(
    "s.translate(TRANS)",
    globals={"s": s, "TRANS": TRANS},
    number=10000,
)

print(t1 / t2)   # translate 一般快 3-5 倍

尤其映射表大的时候差距明显。

常见坑

1. maketrans 键值长度必须一样(字符串形式)

str.maketrans("abc", "xy")     # 报错:长度不匹配
str.maketrans("abc", "xyz")    # OK

字典形式没这个限制。

2. translate 对 bytes 用 bytes 表

b"abc".translate(bytes.maketrans(b"a", b"x"))

字符串和字节的 translate 不通用。

3. 删除字符时用 None

TRANS = {ord("a"): None}
"abc".translate(TRANS)   # "bc"

或者用 str.maketrans("", "", "a") 生成的表。

一句话总结

看到 str.translate(TRANS) + Base64 的组合,大概率是做 URL-safe Base64 或简单字母表混淆。Python 标准库直接有 urlsafe_b64encode,自己用 translate 主要是历史代码。真正的性能优势在于批量替换比多次 replace 快得多。