DES / 3DES
DES(Data Encryption Standard)和 3DES(Triple DES)是比 AES 更老的对称加密算法。目前在新系统中已基本被 AES 取代,但在一些老接口逆向中仍会遇到。 搜索关键字:CryptoJS.DES、CryptoJS.TripleDES、des-cbc、TripleDES、3DES 特征:块大小为 8 字节(填充后密文长度是 8 的倍数);密钥长度为 8 / 16 / 24 字节。 块大小是关键区分指标:DES/3DES 块大小为 8 字节,AES 为 16 字节。若解密后密文长度是 8 的倍数而非 16,优先怀疑 D
官方文档:https://csrc.nist.gov/publications/detail/sp/800/67/rev2/final
适用场景:JS 逆向中识别和还原老接口中的 DES/3DES 加密参数
DES(Data Encryption Standard)和 3DES(Triple DES)是比 AES 更老的对称加密算法。目前在新系统中已基本被 AES 取代,但在一些老接口逆向中仍会遇到。
对比
| 算法 | 密钥长度 | 块大小 | 安全性 |
|---|---|---|---|
| DES | 64 位(有效 56 位,8 字节) | 8 字节 | 已不安全 |
| 3DES | 128 / 192 位(16 / 24 字节) | 8 字节 | 较弱,不推荐 |
| AES | 128 / 192 / 256 位 | 16 字节 | 安全,推荐 |
Python 实现
from Crypto.Cipher import DES, DES3
from Crypto.Util.Padding import pad, unpad
import base64
# ── DES ──────────────────────────────────────────────────────
def des_cbc_encrypt(plaintext: str, key: str, iv: str) -> str:
"""DES-CBC 加密,key 和 iv 均为 8 字节"""
key_b = key.encode('utf-8') # 必须是 8 字节
iv_b = iv.encode('utf-8') # 必须是 8 字节
cipher = DES.new(key_b, DES.MODE_CBC, iv_b)
return base64.b64encode(cipher.encrypt(pad(plaintext.encode(), DES.block_size))).decode()
def des_cbc_decrypt(ciphertext_b64: str, key: str, iv: str) -> str:
key_b = key.encode('utf-8')
iv_b = iv.encode('utf-8')
data = base64.b64decode(ciphertext_b64)
cipher = DES.new(key_b, DES.MODE_CBC, iv_b)
return unpad(cipher.decrypt(data), DES.block_size).decode('utf-8')
def des_ecb_encrypt(plaintext: str, key: str) -> str:
key_b = key.encode('utf-8')
cipher = DES.new(key_b, DES.MODE_ECB)
return base64.b64encode(cipher.encrypt(pad(plaintext.encode(), DES.block_size))).decode()
def des_ecb_decrypt(ciphertext_b64: str, key: str) -> str:
key_b = key.encode('utf-8')
cipher = DES.new(key_b, DES.MODE_ECB)
return unpad(cipher.decrypt(base64.b64decode(ciphertext_b64)), DES.block_size).decode()
# ── 3DES ─────────────────────────────────────────────────────
def des3_cbc_encrypt(plaintext: str, key: str, iv: str) -> str:
"""3DES-CBC 加密,key 为 16 或 24 字节,iv 为 8 字节"""
key_b = key.encode('utf-8') # 16 或 24 字节
iv_b = iv.encode('utf-8') # 8 字节
cipher = DES3.new(key_b, DES3.MODE_CBC, iv_b)
return base64.b64encode(cipher.encrypt(pad(plaintext.encode(), DES3.block_size))).decode()
def des3_cbc_decrypt(ciphertext_b64: str, key: str, iv: str) -> str:
key_b = key.encode('utf-8')
iv_b = iv.encode('utf-8')
data = base64.b64decode(ciphertext_b64)
cipher = DES3.new(key_b, DES3.MODE_CBC, iv_b)
return unpad(cipher.decrypt(data), DES3.block_size).decode('utf-8')
def des3_ecb_encrypt(plaintext: str, key: str) -> str:
key_b = key.encode('utf-8')
cipher = DES3.new(key_b, DES3.MODE_ECB)
return base64.b64encode(cipher.encrypt(pad(plaintext.encode(), DES3.block_size))).decode()
# 使用示例
key8 = '12345678' # 8 字节 DES key
iv8 = '87654321' # 8 字节 IV
key24 = '123456789012345678901234' # 24 字节 3DES key
ct = des_cbc_encrypt('hello world', key8, iv8)
pt = des_cbc_decrypt(ct, key8, iv8)
ct3 = des3_cbc_encrypt('hello world', key24, iv8)
pt3 = des3_cbc_decrypt(ct3, key24, iv8)
JavaScript 实现
使用 CryptoJS
const CryptoJS = require('crypto-js');
// ── DES-CBC ──────────────────────────────────────────────────
function desEncrypt(plaintext, key, iv) {
const keyWA = CryptoJS.enc.Utf8.parse(key); // 8 字节
const ivWA = CryptoJS.enc.Utf8.parse(iv); // 8 字节
return CryptoJS.DES.encrypt(plaintext, keyWA, {
iv: ivWA,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
function desDecrypt(ciphertext, key, iv) {
const keyWA = CryptoJS.enc.Utf8.parse(key);
const ivWA = CryptoJS.enc.Utf8.parse(iv);
return CryptoJS.DES.decrypt(ciphertext, keyWA, {
iv: ivWA,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}
// ── DES-ECB ──────────────────────────────────────────────────
function desEcbEncrypt(plaintext, key) {
const keyWA = CryptoJS.enc.Utf8.parse(key);
return CryptoJS.DES.encrypt(plaintext, keyWA, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
// ── 3DES-CBC ─────────────────────────────────────────────────
function tripleDesEncrypt(plaintext, key, iv) {
const keyWA = CryptoJS.enc.Utf8.parse(key); // 16 或 24 字节
const ivWA = CryptoJS.enc.Utf8.parse(iv); // 8 字节
return CryptoJS.TripleDES.encrypt(plaintext, keyWA, {
iv: ivWA,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
function tripleDesDecrypt(ciphertext, key, iv) {
const keyWA = CryptoJS.enc.Utf8.parse(key);
const ivWA = CryptoJS.enc.Utf8.parse(iv);
return CryptoJS.TripleDES.decrypt(ciphertext, keyWA, {
iv: ivWA,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}
// 使用示例
const key8 = '12345678';
const iv8 = '87654321';
const key16 = '1234567890abcdef';
console.log(desEncrypt('hello', key8, iv8));
console.log(tripleDesEncrypt('hello', key16, iv8));
使用 Node.js 内置 crypto
const crypto = require('crypto');
function desEncrypt(plaintext, key, iv) {
const cipher = crypto.createCipheriv('des-cbc',
Buffer.from(key, 'utf8'),
Buffer.from(iv, 'utf8')
);
return cipher.update(plaintext, 'utf8', 'base64') + cipher.final('base64');
}
function des3Encrypt(plaintext, key, iv) {
// Node.js 中 3DES 算法名:des-ede3-cbc(24字节key)或 des-ede-cbc(16字节key)
const algo = key.length === 24 ? 'des-ede3-cbc' : 'des-ede-cbc';
const cipher = crypto.createCipheriv(algo,
Buffer.from(key, 'utf8'),
Buffer.from(iv, 'utf8')
);
return cipher.update(plaintext, 'utf8', 'base64') + cipher.final('base64');
}
// Node.js 支持的 DES 算法名
// des-cbc : DES CBC
// des-ecb : DES ECB
// des-ede3-cbc : 3DES CBC(24字节key)
// des-ede-cbc : 3DES CBC(16字节key,k1=k3)
识别特征
搜索关键字:CryptoJS.DES、CryptoJS.TripleDES、des-cbc、TripleDES、3DES
特征:块大小为 8 字节(填充后密文长度是 8 的倍数);密钥长度为 8 / 16 / 24 字节。
最佳实践
块大小是关键区分指标:DES/3DES 块大小为 8 字节,AES 为 16 字节。若解密后密文长度是 8 的倍数而非 16,优先怀疑 DES/3DES;再配合密钥长度(8/16/24 字节)确认。
3DES 的三种密钥模式:3DES 密钥长度决定安全强度:24 字节(3 个独立密钥)= 最安全;16 字节(K1=K3)= 最常用;8 字节(K1=K2=K3)= 退化为 DES。逆向时要准确提取密钥长度。
Python 还原用 pycryptodome 而非 pyDes:pyDes 库老旧且速度慢,推荐 from Crypto.Cipher import DES, DES3,API 与 AES 一致,迁移成本低。
from Crypto.Cipher import DES3
from Crypto.Util.Padding import unpad
cipher = DES3.new(key, DES3.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size)
JS 侧的 CryptoJS.DES 默认 CBC + PKCS7:还原时先假设 CBC 模式,若结果不对再尝试 ECB(无 IV)。
3DES EDE 与 EEE 模式:JS 逆向中几乎全是 EDE(加-解-加)模式;EEE(加-加-加)极少见,若 EDE 不对可尝试 EEE。
常见陷阱
陷阱:3DES 密钥长度判断错误
现象: 用提取的 24 字节密钥解密失败,改为 16 字节正常。
原因: 代码中密钥字符串长度为 24 字符,但每个字符是十六进制(2 字符 = 1 字节),实际 12 字节;或密钥是 Base64 编码,需先解码。
解决: 确认密钥的编码格式(原始字节 / Hex / Base64)后再计算实际长度;key.length 是字符数不是字节数。
陷阱:ECB 和 CBC 模式混淆
现象: 提取了 IV 用 CBC 解密失败;去掉 IV 用 ECB 也不对。
原因: 部分实现虽然代码里写了 MODE_CBC 但 IV 全为零字节(\x00 * 8),与不用 IV 的 ECB 行为不同。
解决: 检查 JS 中 IV 的实际值;若 IV 是空字符串或空 WordArray,CryptoJS 可能默认使用全零 IV,Python 侧对应 iv = b'\x00' * 8。
陷阱:DES 密钥奇偶校验位被修改
现象: 复制了 8 字节密钥但解密失败,用其他工具解密正常。
原因: DES 规范要求密钥每字节最低位为奇偶校验位,部分实现会自动调整这些位,导致实际密钥与提取值有细微差异。
解决: pycryptodome 默认忽略校验位;若使用 pyDes,需注意其是否校验密钥合法性。