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 报错或结果异常,正确做法是先 encodeURIComponentTextEncoder 转字节再 Base64。
解决: 确认 JS 侧的 Base64 输入是字符串还是 Uint8Array;Python 侧对应 s.encode('utf-8')s.encode('latin-1')


参见

MD5
SHA
代码片段大全
加密算法识别

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog