Base64 与编码
Base64 是编码方案而非加密,任何人都可以解码。但在逆向中,Base64 几乎出现在每一个加密场景中(作为加密结果的传输格式),因此必须熟练掌握其各种变体。 使用字符集:A-Z a-z 0-9 + /,末尾用 = 填充至 4 的倍数。 遇到不可打印字符先判断编码:抓包响应体乱码时,优先尝试 Base64 解码;若结果仍乱码,再尝试 Hex 解码、zlib/gzip 解压,排除组合编码(如先 AES 再 Base64)。 URL-safe Base64 是最常见变体:搜索 + 被替换为 -、/ 被替换为 _ 的 Base64 字符串;Python 解码
官方文档:https://www.rfc-editor.org/rfc/rfc4648
适用场景:JS 逆向中识别和处理 Base64 及各种编码变体
Base64 是编码方案而非加密,任何人都可以解码。但在逆向中,Base64 几乎出现在每一个加密场景中(作为加密结果的传输格式),因此必须熟练掌握其各种变体。
标准 Base64
使用字符集:A-Z a-z 0-9 + /,末尾用 = 填充至 4 的倍数。
Python 实现
import base64
# ── 标准 Base64 ────────────────────────────────────────────────
# 编码
def b64_encode(data: str) -> str:
return base64.b64encode(data.encode('utf-8')).decode()
# 解码
def b64_decode(data: str) -> str:
return base64.b64decode(data).decode('utf-8')
# 对字节数据编解码
def b64_encode_bytes(data: bytes) -> str:
return base64.b64encode(data).decode()
def b64_decode_to_bytes(data: str) -> bytes:
return base64.b64decode(data)
# ── URL 安全 Base64(+ → -,/ → _,去掉 =)──────────────────
def b64url_encode(data: str) -> str:
return base64.urlsafe_b64encode(data.encode()).decode().rstrip('=')
def b64url_decode(data: str) -> str:
# 补齐 = 号
pad = 4 - len(data) % 4
if pad != 4:
data += '=' * pad
return base64.urlsafe_b64decode(data).decode('utf-8')
# ── 自定义字母表 Base64 ──────────────────────────────────────
def custom_b64_encode(data: str, alphabet: str) -> str:
"""使用自定义字母表编码"""
standard = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
encoded = base64.b64encode(data.encode()).decode()
table = str.maketrans(standard, alphabet)
return encoded.translate(table)
def custom_b64_decode(data: str, alphabet: str) -> str:
standard = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
table = str.maketrans(alphabet, standard)
return base64.b64decode(data.translate(table)).decode()
# 示例:字母表被打乱的 Base64
my_alphabet = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/'
enc = custom_b64_encode('hello world', my_alphabet)
dec = custom_b64_decode(enc, my_alphabet)
# ── 容错解码(处理非标准 Base64 字符串)────────────────────────
def safe_b64_decode(data: str) -> bytes:
"""处理缺少填充的 Base64 字符串"""
data = data.replace('-', '+').replace('_', '/')
pad = 4 - len(data) % 4
if pad != 4:
data += '=' * pad
return base64.b64decode(data)
JavaScript 实现
浏览器原生
// 编码
btoa('hello world') // 'aGVsbG8gd29ybGQ='
// 解码
atob('aGVsbG8gd29ybGQ=') // 'hello world'
// 处理中文(btoa 不支持 Unicode,需先转码)
function b64Encode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16))
));
}
function b64Decode(str) {
return decodeURIComponent(Array.from(atob(str),
c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0')
).join(''));
}
// URL 安全 Base64
function b64UrlEncode(str) {
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
function b64UrlDecode(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return atob(str);
}
Node.js
// 编码
Buffer.from('hello world').toString('base64') // 'aGVsbG8gd29ybGQ='
// 解码
Buffer.from('aGVsbG8gd29ybGQ=', 'base64').toString() // 'hello world'
// URL 安全 Base64
Buffer.from('hello').toString('base64url') // 无 = 号的 URL 安全版
// 字节数组转 Base64
Buffer.from([0x48, 0x65, 0x6c]).toString('base64')
// 自定义字母表(需手动替换)
function customB64Encode(data, alphabet) {
const standard = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let result = Buffer.from(data).toString('base64');
return result.split('').map(c => {
const idx = standard.indexOf(c);
return idx >= 0 ? alphabet[idx] : c;
}).join('');
}
CryptoJS 中的 Base64
const CryptoJS = require('crypto-js');
// WordArray 转 Base64
const wordArray = CryptoJS.enc.Utf8.parse('hello');
const b64 = wordArray.toString(CryptoJS.enc.Base64);
// Base64 转 WordArray
const wa = CryptoJS.enc.Base64.parse('aGVsbG8=');
// Hex 转 Base64
const hex = '48656c6c6f';
const wa2 = CryptoJS.enc.Hex.parse(hex);
const b64str = wa2.toString(CryptoJS.enc.Base64);
URL 编码
from urllib.parse import quote, unquote, urlencode, parse_qs
# 编码(默认不编码 /)
quote('hello world & foo=bar') # 'hello%20world%20%26%20foo%3Dbar'
# 编码所有特殊字符(包括 /)
quote('https://example.com/path', safe='')
# 解码
unquote('hello%20world') # 'hello world'
# 将字典编码为 query string
urlencode({'a': 1, 'b': 'hello world'}) # 'a=1&b=hello+world'
# 解析 query string
parse_qs('a=1&b=hello+world') # {'a': ['1'], 'b': ['hello world']}
// 编码
encodeURIComponent('hello world & a=b') // 'hello%20world%20%26%20a%3Db'
encodeURI('https://example.com/path?a=1') // 只编码特殊字符,不编码 URL 结构字符
// 解码
decodeURIComponent('hello%20world') // 'hello world'
// 构造 query string
new URLSearchParams({a: 1, b: 'hello world'}).toString() // 'a=1&b=hello+world'
// 解析 query string
Object.fromEntries(new URLSearchParams('a=1&b=hello')) // {a: '1', b: 'hello'}
Hex 编解码
# 字符串转 Hex
'hello'.encode().hex() # '68656c6c6f'
bytes.fromhex('68656c6c6f').decode() # 'hello'
# 整数转 Hex
hex(255) # '0xff'
format(255, '02x') # 'ff'
format(255, '08x') # '000000ff'(补零到8位)
// 字符串转 Hex
function strToHex(str) {
return Array.from(Buffer.from(str, 'utf8'))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Hex 转字符串
function hexToStr(hex) {
return Buffer.from(hex, 'hex').toString('utf8');
}
// CryptoJS
CryptoJS.enc.Utf8.parse('hello').toString(CryptoJS.enc.Hex); // '68656c6c6f'
CryptoJS.enc.Hex.parse('68656c6c6f').toString(CryptoJS.enc.Utf8); // 'hello'
各编码格式对照
| 原始数据 | Hex | Base64 | URL 编码 |
|---|---|---|---|
hello |
68656c6c6f |
aGVsbG8= |
hello |
hello world |
68656c6c6f20776f726c64 |
aGVsbG8gd29ybGQ= |
hello%20world |
a&b=c |
— | — | a%26b%3Dc |
最佳实践
遇到不可打印字符先判断编码:抓包响应体乱码时,优先尝试 Base64 解码;若结果仍乱码,再尝试 Hex 解码、zlib/gzip 解压,排除组合编码(如先 AES 再 Base64)。
URL-safe Base64 是最常见变体:搜索 + 被替换为 -、/ 被替换为 _ 的 Base64 字符串;Python 解码时用 base64.urlsafe_b64decode(s + '==') 处理(补齐 padding)。
自定义 Base64 字母表是加密的一种:若标准 Base64 解码结果不对,检查代码中是否有自定义字母表(长度 64 的字符串数组),用标准字母表逐字符对应翻译后再解码。
Hex 编码搜索 toString(16) 或 join(''):JS 常用 Array.from(bytes).map(b => b.toString(16).padStart(2,'0')).join('') 将字节转 Hex,搜索这段模式可定位编码逻辑。
URL 编码注意二次编码:参数经过两次 encodeURIComponent 时,% 本身也被编码为 %25,导致 %2F 变成 %252F;解码时需调用两次 decodeURIComponent。
常见陷阱
陷阱:标准 Base64 解码缺 padding 报错
现象: Python base64.b64decode(s) 抛出 binascii.Error: Incorrect padding。
原因: JS 的 btoa() 和部分 Base64 库会省略末尾 = 填充字符。
解决: base64.b64decode(s + '==') — 多余的 = 不影响解码,缺少的 = 才会报错。
陷阱:URL 编码大小写不一致
现象: Python urllib.parse.quote() 输出 %2F,而服务端期望 %2f(小写)。
原因: RFC 3986 规定 Hex 字符不区分大小写,但部分服务端做了严格匹配;JS encodeURIComponent 输出大写,Python quote 也输出大写,但某些老接口用小写。
解决: 根据抓包结果选择格式;Python 可用 .lower() 将 %XX 转小写:re.sub(r'%[0-9A-F]{2}', lambda m: m.group().lower(), encoded)。
陷阱:字符串 Base64 vs 字节 Base64 输入不同
现象: 同一段数据,Base64 后结果不同。
原因: 将字符串直接 Base64(btoa('hello'))等价于对 Latin-1 字节编码;若字符串含 Unicode,btoa 报错或结果异常,正确做法是先 encodeURIComponent 或 TextEncoder 转字节再 Base64。
解决: 确认 JS 侧的 Base64 输入是字符串还是 Uint8Array;Python 侧对应 s.encode('utf-8') 或 s.encode('latin-1')。