加密算法基础概念
对称加密中用于加密和解密的秘密字符串。密钥长度决定安全强度。 用于对称加密中引入随机性。每次加密使用不同的 IV,即使明文和密钥相同,密文也不同。 分组密码要求明文长度必须是块大小的整数倍(AES 块大小为 16 字节)。填充用于将不足的部分补齐。 决定分组密码如何处理连续的数据块。 加密后的二进制数据通常需要编码为字符串传输: 逆向中常见的数据类型转换: Python 中:str.encode('utf-8') → bytes JavaScript 中:new TextEncoder().encode(str) → Uint8Array 逆向前先判断算
官方文档:https://csrc.nist.gov/publications/fips
适用场景:JS 逆向前的算法识别基础,理解对称/非对称/哈希的核心差异
算法分类
加密算法
├── 哈希算法(单向,不可逆)
│ ├── MD5
│ ├── SHA-1 / SHA-224 / SHA-256 / SHA-384 / SHA-512
│ └── HMAC(带密钥的哈希)
│
├── 对称加密(加解密用同一密钥)
│ ├── AES(Advanced Encryption Standard)
│ ├── DES / 3DES(已不推荐)
│ └── RC4(流密码,已不推荐)
│
├── 非对称加密(公钥加密,私钥解密)
│ └── RSA
│
└── 编码(非加密,可逆)
├── Base64
└── URL Encode
核心概念
密钥(Key)
对称加密中用于加密和解密的秘密字符串。密钥长度决定安全强度。
| 算法 | 常见密钥长度 |
|---|---|
| AES | 128 / 192 / 256 位(16 / 24 / 32 字节) |
| DES | 64 位(8 字节,实际有效 56 位) |
| 3DES | 128 / 192 位 |
初始化向量(IV,Initialization Vector)
用于对称加密中引入随机性。每次加密使用不同的 IV,即使明文和密钥相同,密文也不同。
- CBC 模式必须有 IV,长度通常与块大小相同(AES 为 16 字节)
- ECB 模式不使用 IV(因此不安全,相同明文 → 相同密文)
- IV 通常随密文一起传输(拼接在密文头部或作为单独字段)
填充(Padding)
分组密码要求明文长度必须是块大小的整数倍(AES 块大小为 16 字节)。填充用于将不足的部分补齐。
| 填充方式 | 说明 | 示例(块大小16,数据13字节,需填3字节) |
|---|---|---|
| PKCS#5 / PKCS#7 | 填充值等于填充字节数 | \x03\x03\x03 |
| ZeroPadding | 填充 \x00 |
\x00\x00\x00 |
| NoPadding | 不填充(明文必须恰好对齐) | — |
| ISO 10126 | 随机字节 + 最后一字节为填充数 | \xXX\xXX\x03 |
PKCS#7 是最常见的填充方式,CryptoJS 默认使用 PKCS#7。
加密模式(Mode)
决定分组密码如何处理连续的数据块。
| 模式 | 全称 | 需要 IV | 特点 |
|---|---|---|---|
| ECB | Electronic Codebook | 否 | 最简单,不安全(相同块 → 相同密文) |
| CBC | Cipher Block Chaining | 是 | 最常用,前一块密文作为下一块输入 |
| CFB | Cipher Feedback | 是 | 流模式,可处理任意长度数据 |
| OFB | Output Feedback | 是 | 流模式,错误不传播 |
| CTR | Counter | 是(Nonce) | 流模式,可并行,现代推荐 |
| GCM | Galois/Counter Mode | 是 | CTR + 认证标签,AEAD 模式 |
编码格式
加密后的二进制数据通常需要编码为字符串传输:
| 格式 | 说明 | 示例 |
|---|---|---|
| Hex | 十六进制字符串 | 48656c6c6f |
| Base64 | 64字符编码 | SGVsbG8= |
| Base64URL | URL安全的Base64(+→-, /→_,无=) |
SGVsbG8 |
字节与字符串
逆向中常见的数据类型转换:
字符串 → 字节数组 → 加密运算 → 字节数组 → Hex/Base64 字符串
"hello" → [72,101,...] → AES/MD5 → [...] → "48656c..."
Python 中:str.encode('utf-8') → bytes
JavaScript 中:new TextEncoder().encode(str) → Uint8Array
最佳实践
逆向前先判断算法类别:看密文长度和格式——固定长度 32/40/64 字符十六进制 = 哈希(MD5/SHA-1/SHA-256);变长且带 = 结尾 = 对称加密 Base64 输出;超长字符串(256~512 字符)= RSA。快速分类后再定位具体算法。
哈希类算法不需要密钥,对称类必须找密钥:理清这一点后可以针对性搜索——哈希直接搜函数名,对称加密(AES/DES)需要同时找密钥和 IV,非对称(RSA)需要找公钥。
编码和加密要分开处理:先解码(Base64/Hex/URL 解码)再解密;加密时先加密再编码。混淆后的代码常将编码层和加密层嵌套,逐层剥离比整体还原更高效。
数据类型转换是逆向的核心难点:JS 的 WordArray、Uint8Array、ArrayBuffer、字符串之间的转换频繁出现。固定一套转换工具(如 CryptoJS Encoder 系列),不要每次手写转换代码。
Python 还原时用 pycryptodome 统一处理:pycryptodome 支持 AES/DES/3DES/RSA,API 风格统一,比用多个库更容易维护。
常见陷阱
陷阱:把哈希算法的输出误认为加密数据
现象: 尝试对 32 字符十六进制字符串解密,始终无法还原明文。
原因: MD5/SHA 等哈希是单向函数,数学上不可逆(只能碰撞/彩虹表),不能"解密"。
解决: 确认该字段是哈希还是加密:哈希固定长度且相同输入永远得到相同输出;对称加密密文长度随明文变化。若是哈希,考虑查彩虹表或破解方案,而非解密。
陷阱:忽略字符编码导致前后端不一致
现象: Python 和 JS 用相同密钥和 IV 加密同一字符串,结果不同。
原因: JS String 默认 UTF-16,Python str.encode() 默认 UTF-8;含非 ASCII 字符时两者字节表示不同。
解决: 在 JS 中用 new TextEncoder().encode(str) 明确转 UTF-8 字节后再传入加密函数;Python 用 str.encode('utf-8') 保持一致。
陷阱:忘记区分 padding 方案
现象: AES 解密后末尾有多余字节或报 ValueError: PKCS#7 padding is incorrect。
原因: 加密时使用 PKCS#7(CryptoJS 默认),解密时 unpad 方案不匹配,或密文末尾被截断。
解决: 确认 JS 侧的 padding 配置(CryptoJS.pad.Pkcs7 是默认,CryptoJS.pad.NoPadding 表示不填充),Python 侧用 Crypto.Util.Padding.unpad(data, block_size, style='pkcs7') 对应。