JavaScript逆向之对称加密算法

什么是对称加密

简单来说,对称加密是一种加密技术,其最核心的特点是:加密数据和解密数据,使用的是同一个密钥 (Key)。对称加密主要分为两大类分组密码 (AES、DES) 和 流密码 (ChaCha20、RC4),本文只介绍分组密码。

工作流程

  1. 发送方接收方 必须事先通过一种安全的方式,共享同一个密钥。
  2. 发送方 使用这个共享密钥,将原始数据(明文)转换成一种无法直接阅读的格式(密文)。这个过程叫加密
  3. 发送方 将加密后的密文发送给接收方。即使数据在传输过程中被窃听,窃听者看到的也只是无意义的乱码。
  4. 接收方 收到密文后,使用同一个共享密钥 ,将密文还原成原始的可读数据(明文)。这个过程叫解密

工作模式

首先,我们需要明白为什么需要"工作模式"?像 AES 这样的分组密码 (Block Cipher),其本质是一次只能处理一个固定大小的数据块(对 AES 来说是 128 位,即 16 字节)。但我们现实中要加密的数据(如文件、消息)远远超过 16 字节。工作模式就是定义如何重复使用分组密码算法来安全地加密这些连续的数据块的标准化规则或方案。下面介绍几种最主要、最具代表性的工作模式

模式 (Mode) 全称 (Full Name) 工作原理 (Working Principle) 初始化向量 (IV) 要求 安全性 / 推荐度 并行处理能力 主要特点 / 评价
ECB 电子密码本模式 (Electronic Codebook) 每个块独立加密,明文与密文一一对应。 不使用 (Not Used) 极不安全 ✅ 加/解密均可 缺乏 IV 是其核心缺陷,导致无法隐藏数据模式。绝对禁止使用。
CBC 密码块链接模式 (Cipher Block Chaining) 将前一个密文块与当前明文块异或(XOR)后加密。 需要,必须不可预测 (Required, must be unpredictable) ⚠️ 基本安全 / 谨慎使用 ❌ 加密串行 ✅ 解密并行 曾经的行业标准。IV 的不可预测性对安全性至关重要。需与 HMAC 结合使用。
CFB 密文反馈模式 (Cipher Feedback) 将前一个密文块加密后,与当前明文块异或(XOR)。 需要,必须不可预测 (Required, must be unpredictable) ⚠️ 基本安全 / 较少使用 ❌ 加密串行 ✅ 解密并行 将分组密码变为流密码。加密性能受限,现已被取代。
OFB 输出反馈模式 (Output Feedback) 迭代加密密钥流(将加密函数的输出再次加密),再与明文异或(XOR)。 需要,必须唯一 (Nonce) (Required, must be unique) ⚠️ 基本安全 / 较少使用 ❌ 加/解密均串行 将分组密码变为流密码。性能是主要瓶颈。IV/Nonce 绝不能重复使用。
CTR 计数器模式 (Counter) 加密一个独立的"随机数+计数器"序列生成密钥流,再与明文异或(XOR)。 需要 (Nonce),必须唯一 (Required (Nonce), must be unique) 安全 ✅ 加/解密均可 高性能并行处理。IV/Nonce 的唯一性是安全保证的核心,重复使用会导致灾难性后果。
GCM 伽罗瓦/计数器模式 (Galois/Counter Mode) 基于 CTR 模式进行加密,并额外计算一个认证标签(Tag)以保证完整性。 需要 (Nonce),必须唯一 (Required (Nonce), must be unique) 强烈推荐 ✅ 加/解密均可 现代标准 (AEAD) 。同时提供机密性、完整性和真实性。新项目首选

对称加密算法分类

DES

算法介绍

DES 的全称是 Data Encryption Standard,即数据加密标准。它是一种对称加密算法,在 20 世纪末期曾是全球应用最广泛的加密标准。 DES 属于分组密码 (Block Cipher),它具有以下核心技术参数:

  1. 分组长度 (Block Size): 64 位 (8 字节) DES 每次只能加密 64 位的数据块。如果要加密的数据超过 64 位,就需要通过特定的工作模式(如 CBC, CFB 等)来处理。
  2. 密钥长度 (Key Size): 56 位 这是 DES 最关键,也是最终导致其被淘汰的特点。
    • DES 的密钥表面上是 64 位的,但其中有 8 位是用于奇偶校验的校验位,并不参与加密运算。
    • 因此,实际用于加密的有效密钥长度只有 56 位。
  3. 算法结构: 费斯妥网络 (Feistel Network) DES 的内部结构是一种名为"费斯妥网络"的经典设计。其基本思想是将数据块分成左右两半,然后进行多轮迭代加密。在每一轮中:
    • 使用一个根据主密钥生成的子密钥 (Subkey) 对右半部分数据进行一系列复杂的置换和代换运算。

    • 将运算结果与左半部分数据进行异或 (XOR)。

    • 将左右两半交换位置(最后一轮除外)。

    • 这个过程总共重复 16 轮。 这种结构巧妙之处在于,加密和解密的过程基本相同,只是子密钥的使用顺序相反,这在硬件实现上非常有优势。

DES 在今天被认为是完全不安全的 ,绝对不应用于任何需要保护的场景。其核心致命弱点就是密钥长度太短

JavaScript 实现

需先安装crypto-js

bash 复制代码
npm install crypto-js

DES 是一个分组密码,需要配合工作模式 (如 ECB, CBC) 和填充方式 (Padding) 来使用。CBC 模式是比 ECB 更安全的选择,它需要一个初始化向量 (IV)关键点:

  • crypto-js 要求密钥 (key) 和初始化向量 (IV) 必须是它内部的 WordArray 对象格式。我们需要使用 CryptoJS.enc.Utf8.parse() 来将字符串转换为 WordArray。
  • DES 的密钥长度是 8 字节(64位),IV 长度也是 8 字节。
javascript 复制代码
const CryptoJS = require('crypto-js');

// --- 准备工作 ---
const message = "This is a secret message for DES.";

// 密钥必须是 8 字节(8个 ASCII 字符)
const keyHex = CryptoJS.enc.Utf8.parse("12345678"); 

// IV 也必须是 8 字节
const ivHex = CryptoJS.enc.Utf8.parse("87654321");

// --- 加密 ---
const encrypted = CryptoJS.DES.encrypt(message, keyHex, {
  iv: ivHex,
  mode: CryptoJS.mode.CBC, // 工作模式:CBC
  padding: CryptoJS.pad.Pkcs7 // 填充方式:Pkcs7
});

// 加密后的结果是一个特殊的对象,可以转换为不同格式的字符串
const encryptedBase64 = encrypted.toString(); // 默认输出 Base64
const encryptedHex = encrypted.ciphertext.toString(CryptoJS.enc.Hex); // 输出 Hex

console.log("原始消息:", message);
console.log("-------------------------------------");
console.log("加密后的 Base64:", encryptedBase64);
console.log("加密后的 Hex:", encryptedHex);
console.log("-------------------------------------");


// --- 解密 ---

// 解密时,需要传入之前加密得到的 Base64 字符串
// 密钥和 IV 必须与加密时完全相同
const decrypted = CryptoJS.DES.decrypt(encryptedBase64, keyHex, {
  iv: ivHex,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7
});

// 解密后的结果也是一个 WordArray 对象,需要转换为 UTF8 字符串
const decryptedMessage = decrypted.toString(CryptoJS.enc.Utf8);

console.log("解密后的消息:", decryptedMessage);
console.log("解密是否成功:", message === decryptedMessage);

Python 实现

需先安装pycryptodome

bash 复制代码
pip install pycryptodome

关键点:

  • 密钥 (key) 和初始化向量 (IV) 都必须是字节 (bytes) 类型。
  • DES 的密钥长度是 8 字节(64位),IV 长度也是 8 字节。
  • 加密前,需要对明文进行填充,使其长度是分组大小(8字节)的整数倍。pycryptodome 提供了方便的填充函数。
python 复制代码
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
import base64

# --- 准备工作 ---
message = "This is a secret message for DES."

# 密钥必须是 8 字节
key = b'12345678'  # 前面加 b 表示这是一个 bytes 对象

# IV 也必须是 8 字节
iv = b'87654321'

# --- 加密 ---
def encrypt_des_cbc(msg, key, iv):
    # 1. 创建一个 DES 加密器实例,使用 CBC 模式
    cipher = DES.new(key, DES.MODE_CBC, iv)
    
    # 2. 将消息编码为字节,并进行填充
    #    DES 的块大小是 8 字节
    padded_msg = pad(msg.encode('utf-8'), DES.block_size)
    
    # 3. 执行加密
    encrypted_bytes = cipher.encrypt(padded_msg)
    
    # 4. 将加密后的字节转换为 Base64 编码的字符串以便传输
    return base64.b64encode(encrypted_bytes).decode('utf-8')

# --- 解密 ---
def decrypt_des_cbc(encrypted_b64, key, iv):
    # 1. 将 Base64 字符串解码回字节
    encrypted_bytes = base64.b64decode(encrypted_b64)
    
    # 2. 创建一个 DES 解密器实例,参数与加密时完全相同
    cipher = DES.new(key, DES.MODE_CBC, iv)
    
    # 3. 执行解密
    decrypted_padded_bytes = cipher.decrypt(encrypted_bytes)
    
    # 4. 去除填充,并将字节解码回字符串
    unpadded_bytes = unpad(decrypted_padded_bytes, DES.block_size)
    return unpadded_bytes.decode('utf-8')


# --- 使用示例 ---
encrypted_message = encrypt_des_cbc(message, key, iv)
print(f"原始消息: '{message}'")
print("-" * 40)
print(f"加密后的 Base64: {encrypted_message}")
print("-" * 40)

decrypted_message = decrypt_des_cbc(encrypted_message, key, iv)
print(f"解密后的消息: '{decrypted_message}'")
print(f"解密是否成功: {message == decrypted_message}")

AES

算法介绍

AES 的全称是 Advanced Encryption Standard,即高级加密标准。它是一种对称加密算法中的分组密码 (Block Cipher)。 AES 属于分组密码 (Block Cipher),它具有以下核心技术参数:

  1. 分组长度 (Block Size): 128 位 (16 字节) AES 始终处理 128 位的数据块。这比 DES/3DES 的 64 位块更大,有助于抵抗某些类型的攻击。

  2. 密钥长度 (Key Sizes): 128位、192位 或 256位 这是 AES 最核心的优势之一。它提供了三种密钥长度选项,以满足不同的安全需求:

    • AES-128: 使用 128 位密钥,进行 10 轮加密。
    • AES-192: 使用 192 位密钥,进行 12 轮加密。
    • AES-256: 使用 256 位密钥,进行 14 轮加密。(通常是需要最高安全级别时的推荐之选)
  3. 算法结构: SPN (Substitution-Permutation Network) AES 的内部结构与 DES 的费斯妥网络不同,它采用的是"替换-置换网络"结构。在每一轮加密中,它会对数据块进行四种不同的数学变换:

    • SubBytes: 字节替换,通过一个名为 S-box 的查找表进行非线性替换,是混淆的核心。
    • ShiftRows: 行移位,将数据矩阵的每一行进行循环移位,以实现扩散。
    • MixColumns: 列混淆,对数据矩阵的每一列进行特定的线性变换,进一步扩散数据。
    • AddRoundKey: 轮密钥加,将当前数据块与从主密钥派生出的"轮密钥"进行异或 (XOR) 操作。

这个过程会根据密钥长度重复 10-14 轮,确保了即使输入有微小的变化,输出的密文也会变得面目全非。 AES 是现代对称加密的基石。在开发任何需要加密功能的系统时,AES(通常推荐 AES-256) 配合一个安全的工作模式(如 GCM)是毋庸置疑的最佳选择。

JavaScript 实现

需先安装crypto-js

bash 复制代码
npm install crypto-js
javascript 复制代码
const CryptoJS = require('crypto-js');

// 1. 准备工作:定义加密所需的所有参数
const message = "This is a secret message to be encrypted manually.";

// 密钥 (Key):使用 32 字节 (32 * 8 = 256位) 的字符串,对应 AES-256
const keyString = "MySecretKeyMustBe32Characters123"; 

// 初始化向量 (IV):AES 的 IV 必须是 16 字节
const ivString = "MyUniqueIV16Char";

// 2. 将字符串格式的 Key 和 IV 转换为 CryptoJS 的 WordArray 格式
//    这是 crypto-js 的要求
const key = CryptoJS.enc.Utf8.parse(keyString);
const iv = CryptoJS.enc.Utf8.parse(ivString);

console.log("--- 加解密参数 ---");
console.log("原始消息:", message);
console.log("使用的密钥 (UTF8):", keyString);
console.log("使用的 IV (UTF8):", ivString);
console.log("-----------------------------------------");


// 3. --- 加密 ---
console.log("正在加密...");
const encrypted = CryptoJS.AES.encrypt(message, key, {
  iv: iv,                      // 指定 IV
  mode: CryptoJS.mode.CBC,     // 指定工作模式为 CBC
  padding: CryptoJS.pad.Pkcs7  // 指定填充方式为 Pkcs7
});

// 将加密结果转换为 Base64 字符串,便于传输和存储
const encryptedBase64 = encrypted.toString();
console.log("加密后的 Base64 字符串:", encryptedBase64);
console.log("-----------------------------------------");


// 4. --- 解密 ---
console.log("正在解密...");
// 解密时,必须使用与加密时完全相同的 Key, IV, Mode 和 Padding
const decryptedBytes = CryptoJS.AES.decrypt(encryptedBase64, key, {
  iv: iv,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7
});

// 将解密后的 WordArray 对象转换回 UTF-8 字符串
const decryptedMessage = decryptedBytes.toString(CryptoJS.enc.Utf8);
console.log("解密后的消息:", decryptedMessage);
console.log("-----------------------------------------");


// 5. --- 验证 ---
console.log("验证解密是否成功:", message === decryptedMessage);

Python 实现

需先安装pycryptodome

bash 复制代码
pip install pycryptodome
python 复制代码
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

# --- 1. 准备工作:定义加密所需的所有参数 ---

# 原始消息
message = "This is a secret message to be encrypted manually."

# 密钥 (Key):必须是字节(bytes)类型。
# 32 字节的密钥对应 AES-256。
key = b'MySecretKeyMustBe32Characters123'

# 初始化向量 (IV):必须是字节(bytes)类型,且长度为 16 字节。
iv = b'MyUniqueIV16Char'

print("--- 加解密参数 ---")
print(f"原始消息: '{message}'")
print(f"使用的密钥: {key}")
print(f"使用的 IV: {iv}")
print("-----------------------------------------")


# --- 2. 加密函数 ---
def encrypt(plain_text, key, iv):
    """
    使用 AES-CBC 模式加密文本
    """
    # 将明文消息编码为字节
    plain_text_bytes = plain_text.encode('utf-8')
    
    # 创建一个 AES 加密器实例
    cipher = AES.new(key, AES.MODE_CBC, iv)
    
    # 对明文进行填充,使其长度是块大小(16字节)的整数倍
    padded_bytes = pad(plain_text_bytes, AES.block_size)
    
    # 执行加密
    encrypted_bytes = cipher.encrypt(padded_bytes)
    
    # 将加密后的字节转换为 Base64 编码的字符串,以便传输
    return base64.b64encode(encrypted_bytes).decode('utf-8')

# --- 3. 解密函数 ---
def decrypt(encrypted_b64, key, iv):
    """
    使用 AES-CBC 模式解密文本
    """
    # 将 Base64 字符串解码回字节
    encrypted_bytes = base64.b64decode(encrypted_b64)
    
    # 创建一个 AES 解密器实例,参数与加密时完全相同
    cipher = AES.new(key, AES.MODE_CBC, iv)
    
    # 执行解密
    decrypted_padded_bytes = cipher.decrypt(encrypted_bytes)
    
    # 去除填充,得到原始的字节数据
    unpadded_bytes = unpad(decrypted_padded_bytes, AES.block_size)
    
    # 将字节解码回我们可读的字符串
    return unpadded_bytes.decode('utf-8')


# --- 4. 执行并验证 ---
print("正在加密...")
encrypted_message = encrypt(message, key, iv)
print(f"加密后的 Base64 字符串: {encrypted_message}")
print("-----------------------------------------")

print("正在解密...")
decrypted_message = decrypt(encrypted_message, key, iv)
print(f"解密后的消息: '{decrypted_message}'")
print("-----------------------------------------")

print(f"验证解密是否成功: {message == decrypted_message}")

逆向技巧

  • 对称加密可以尝试使用关键词 encrypt、decrypt定位加密解密位置。
  • 可以使用《JavaScript逆向摘要算法》中介绍的对比的方法判断网页中使用的是否是标准算法。
  • 加密的密钥要么是在前端写死的,要么是通过接口从后端传递过来的,也有可能是在前端生成的传递给后端的。

案例

使用全局搜索"decrypt(",并在相应位置打上断点,可以定位到解密的位置

相关推荐
一枚前端小能手2 小时前
🛡️ Token莫名其妙就泄露了?JWT安全陷阱防不胜防
前端·javascript·安全
李游Leo2 小时前
JavaScript事件机制与性能优化:防抖 / 节流 / 事件委托 / Passive Event Listeners 全解析
开发语言·javascript·性能优化
复苏季风2 小时前
Vue3 小白的疑惑:为什么用 const 定义的变量还能改?
前端·javascript·vue.js
扉川川2 小时前
File和Blob对象的区别
javascript
Mintopia2 小时前
在 Next.js 中接入 Google Analytics 与 PostHog —— 一场“数据偷窥”的艺术演出
前端·javascript·next.js
遂心_2 小时前
React useState:20分钟彻底掌握这个让你"状态满满"的Hook
前端·javascript·react.js
Mintopia2 小时前
AIGC驱动的Web界面设计:技术逻辑与用户体验平衡
前端·javascript·aigc
盏茶作酒292 小时前
浅拷贝和深拷贝
前端·javascript
鹏多多3 小时前
Vue项目i18n国际化多语言切换方案实践
前端·javascript·vue.js