1. 项目概述与核心价值

最近在分析一些电商平台的自动化流程时,Temu的登录接口引起了我的兴趣。作为一个快速崛起的电商平台,其前端安全机制,特别是登录环节的加密方式,是理解其整体安全架构和实现自动化操作的关键入口。这个项目,就是深入其登录流程,逆向分析其前端JavaScript代码,最终用Python实现一套纯本地计算的登录方案。简单说,就是搞清楚用户在网页上输入账号密码点击登录后,数据在到达服务器之前,到底经历了怎样的“变形”,并自己动手把这个“变形”过程复现出来。

为什么这件事有价值?对于开发者而言,这不仅仅是满足技术好奇心。首先,它是理解现代Web应用前端安全实践的绝佳案例,尤其是非对称加密(RSA)在前端的应用。其次,在合规的前提下(例如,用于自家店铺的自动化管理、数据监控或测试),掌握这套机制意味着你可以构建更稳定、不依赖浏览器图形界面的后台服务,效率更高,资源占用更低。最后,这个过程本身锻炼的是逆向工程、网络协议分析和密码学应用的综合能力,这些技能在安全研究、爬虫工程和自动化测试领域都是硬通货。

2. 逆向分析的核心思路与工具准备

逆向分析的核心目标,是找到从明文密码到最终提交的加密字符串之间的完整转换链条。这个过程通常隐藏在压缩和混淆过的前端JavaScript代码中。我们的思路是“顺藤摸瓜”:从网络请求的终点(加密结果)出发,反向追踪生成这个结果的JavaScript函数调用栈。

2.1 核心工具:浏览器开发者工具

这是我们的主战场,几乎所有关键信息都从这里获取。

  1. 网络面板(Network) :这是起点。在登录前打开,务必勾选 “保留日志(Preserve log)” ,防止页面跳转时请求记录被清空。然后执行登录操作,在请求列表中找到登录接口(通常是包含 login signin 等关键词的 POST 请求)。重点关注这个请求的 请求负载(Payload) ,你会看到类似 password: “aBcDeFgHiJkL…(一长串密文)” 的字段。这个密文就是我们的终极目标。
  2. 源代码面板(Sources) :找到加密逻辑的关键。在网络面板中,右键点击那个登录请求,选择 “在源代码面板中显示” 或类似选项。浏览器会自动定位到发起这个网络请求的JavaScript代码行。通常,这里是一个 fetch XMLHttpRequest 调用,其参数中就包含了加密后的密码。
  3. 调用栈(Call Stack) :在源代码面板定位到的代码行设置断点,重新触发登录。程序会在断点处暂停。此时查看调用栈面板,你可以看到函数一层层被调用的顺序。从最顶层(发起请求处)向下逐层追溯,就能找到执行实际加密操作的函数。
  4. 控制台(Console) :用于实时执行和测试我们找到的JavaScript代码片段,验证加密结果是否与网络请求中的一致。

2.2 辅助工具:格式化与搜索

前端代码通常经过压缩(一行,变量名短)和混淆(变量名、函数名被替换成无意义的字符)。我们需要:

  • 代码美化(Pretty Print) :在源代码面板,点击左下角的 {} 图标,可以将压缩的代码格式化,变得可读。
  • 全局搜索 :在格式化后的代码中,搜索网络请求负载中的关键字段名,如 password encrypt RSA publicKey 等,可以快速定位相关代码区域。

注意 :逆向工程应仅用于学习、安全研究或在拥有明确授权的系统上进行测试。未经授权对他人系统进行逆向分析或攻击可能违反法律和服务条款。

3. 核心加密逻辑解析与Python实现

通过上述逆向流程,我们通常会发现Temu登录采用了 RSA公钥加密 机制。这是一种非对称加密,前端使用一个公开的、从服务器获取的公钥对密码进行加密,只有持有对应私钥的服务器才能解密。这有效防止了密码在传输过程中被窃听。

3.1 逆向得到的典型流程

  1. 获取公钥 :页面加载时或登录前,前端会通过一个API请求从服务器获取一个RSA公钥。这个公钥通常是PEM格式的字符串,可能包含 -----BEGIN PUBLIC KEY----- 这样的头尾标记。
  2. 加密参数准备 :公钥可能被直接使用,也可能需要经过一些处理,比如移除换行符、拼接固定字符串等。
  3. 执行加密 :前端调用JavaScript的加密库(常见的是 jsencrypt node-rsa 等)的加密方法,传入明文密码和处理后的公钥,得到Base64编码的密文。
  4. 提交 :将密文作为 password 字段的值,连同用户名等其他参数,提交到登录接口。

3.2 Python纯算实现的关键步骤

我们的目标是在不调用任何浏览器或JavaScript引擎的情况下,在Python环境中复现这一过程。这里以最常见的 jsencrypt 库对应的RSA PKCS#1 v1.5 加密模式为例。

第一步:安装必要的库 我们需要 rsa cryptography 库来处理RSA加密。这里推荐功能更全面的 cryptography

pip install cryptography

第二步:处理公钥字符串 从逆向得到的代码中,找到服务器返回的公钥字符串。它可能需要清理。

import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

# 假设这是从网络请求或页面源码中提取的公钥字符串
# 注意:实际逆向中,这个字符串可能是动态获取的,需要模拟相应的请求。
public_key_str = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
...(此处省略中间部分)...
qGhCXYQeGq6J9J0p6iVq6K4/8zJfRz3c6C7ZvK7l5+5gJwIDAQAB
-----END PUBLIC KEY-----"""

# 加载公钥
public_key = serialization.load_pem_public_key(public_key_str.encode())

第三步:实现加密函数 jsencrypt 库默认使用 PKCS#1 v1.5 填充模式。我们需要在Python中使用对应的填充方案。

def encrypt_password(password: str, public_key) -> str:
    """
    使用RSA公钥加密密码,模拟jsencrypt的行为。
    
    参数:
        password: 明文密码字符串
        public_key: 加载后的公钥对象
    
    返回:
        Base64编码的加密字符串
    """
    # 将密码转换为字节
    message = password.encode('utf-8')
    
    # 使用PKCS1v15填充进行加密
    # 注意:OAEP是更安全的填充方式,但jsencrypt默认使用PKCS1v15。
    encrypted = public_key.encrypt(
        message,
        padding.PKCS1v15() # 这是关键,必须与前端使用的填充方式一致
    )
    
    # 将加密后的字节进行Base64编码,得到最终密文
    ciphertext_b64 = base64.b64encode(encrypted).decode('utf-8')
    return ciphertext_b64

# 使用示例
plain_password = "YourPassword123"
encrypted_password = encrypt_password(plain_password, public_key)
print(f"加密后的密码: {encrypted_password}")

第四步:模拟登录请求 得到加密密码后,就可以用 requests 库模拟登录了。

import requests
import json

login_url = "https://api.temu.com/your-login-endpoint" # 逆向分析得到的真实登录接口
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
    "Content-Type": "application/json",
    # 可能还需要其他Headers,如X-Requested-With, Referer等,需从浏览器请求中复制。
}

payload = {
    "username": "your_username",
    "password": encrypted_password, # 使用我们计算出的密文
    # ... 其他必要的参数,如captchaToken, client_id等
}

session = requests.Session()
# 可能需要先请求公钥或处理其他前置接口
# session.get(...)

response = session.post(login_url, headers=headers, data=json.dumps(payload))
print(response.status_code)
print(response.json())

3.3 实操心得与关键细节

  1. 公钥的动态性 :切勿将公钥硬编码在代码中。它很可能每天甚至每次会话都会变化。你的Python脚本必须首先模拟获取公钥的请求(通常是GET某个特定API),从中提取最新的公钥字符串。这个请求的URL和参数也需要通过逆向分析获得。
  2. 填充模式是灵魂 :RSA加密本身是确定的,但填充模式决定了加密结果。 jsencrypt 默认使用 PKCS1v15 ,而不是更现代的 OAEP 。如果填充模式选错,服务器将无法解密,返回“密码错误”。这是逆向后移植中最常见的坑。
  3. 编码一致性 :确保每一步的字符编码一致,通常都是 UTF-8 。从明文到字节,加密后到Base64字符串,都要明确指定。
  4. 额外的参数与签名 :复杂的登录流程可能不止加密密码。用户名、时间戳或其他参数可能被组合起来进行某种签名(如HMAC-SHA256)。你需要仔细分析登录请求的完整负载,看是否有 sign token 等字段,并逆向其生成算法。
  5. 会话管理 :登录成功后,服务器返回的 sessionId cookie (如 Set-Cookie 头)需要被 requests.Session() 对象自动管理或手动保存,用于后续的授权请求。

4. 常见问题排查与调试技巧

即使按照流程操作,也可能无法成功登录。以下是几个排查方向:

问题1:加密后的密码长度或格式与浏览器不一致。

  • 排查 :在浏览器开发者工具的“控制台”中,在加密函数执行前打入断点,分别记录输入的明文、使用的公钥字符串、以及加密函数的输出。在你的Python脚本中,打印出对应阶段的中间值进行比对。
  • 可能原因
    • 公钥处理不一致 :浏览器中的公钥可能被去掉了头尾标记或换行符。检查逆向代码中对公钥的预处理步骤。
    • 填充模式错误 :确认前端使用的是 PKCS1v15 还是 OAEP 。可以尝试在Python中分别用两种模式加密同一个短字符串,看结果哪个与浏览器匹配。
    • 加密库差异 :极少数情况下,前端可能使用了自定义的RSA实现或非标准参数。这时需要更深入阅读其JavaScript加密函数。

问题2:登录请求返回“参数错误”或“无效请求”。

  • 排查 :使用抓包工具(如Fiddler、Charles)或浏览器Network面板,完整对比你的Python请求和浏览器请求的每一个细节。
  • 对比项
    • 请求URL :是否完全一致,包括查询参数。
    • HTTP方法 :一定是POST吗?
    • 请求头(Headers) User-Agent , Content-Type , Origin , Referer , X-Requested-With 等是否缺失或不同? Cookie 头是否包含了必要的会话信息?
    • 请求体(Body) :除了 username password ,是否遗漏了 captcha_token client_id request_id timestamp 等字段?这些字段的值是如何生成的?

问题3:请求被风控,返回验证码或直接拒绝。

  • 原因 :你的Python脚本的请求特征(如IP、Header指纹、行为序列)与真实浏览器差异太大。
  • 应对策略
    • 完善Headers :从浏览器中复制完整的Headers序列,特别是 User-Agent Accept-Language Sec-* 系列头。
    • 模拟更真实的行为 :在登录前,先访问几个页面(如首页、商品页),获取并携带必要的Cookie。
    • 处理验证码 :如果触发图形验证码,则需要集成打码平台或OCR识别。如果触发滑块验证码,则难度极大,通常需要考虑其他方案,如使用自动化浏览器框架(如Playwright、Selenium)来绕过。
    • 控制请求频率 :过于频繁的登录尝试会触发风控。

问题4:公钥获取接口有签名验证。

  • 现象 :请求获取公钥的API时,返回错误或空数据。
  • 解决 :观察获取公钥的请求,它很可能也需要一个由页面参数(如时间戳、页面URL)计算出的签名。你需要先逆向这个签名的生成算法,才能成功拿到公钥。这通常意味着逆向工作从登录接口向前延伸到了更早的页面初始化阶段。

调试技巧表

问题现象 可能原因 排查步骤
加密结果长度不同 公钥不一致、填充模式错误 1. 比对浏览器和Python加载的公钥字符串。
2. 在浏览器控制台和Python中,用同一公钥加密同一短字符串(如”test”),对比结果。
登录返回“密码错误” 加密逻辑错误、密码原文错误 1. 确认明文密码正确。
2. 确认加密函数、填充模式与前端完全一致。
3. 检查是否有密码加盐(Salt)或拼接其他字符串后再加密。
返回“参数缺失” 请求负载字段不全 1. 完整对比浏览器登录请求的Payload和你构造的字典。
2. 检查是否有动态生成的字段(如时间戳、随机数)。
请求被重定向到验证页 风控触发、Cookie缺失 1. 检查请求头是否完整,特别是Referer和User-Agent。
2. 检查是否在登录前需要访问特定页面建立会话。
3. 考虑使用IP代理。

5. 从逆向到稳定服务:工程化考量

当加密登录的Python脚本跑通后,若想用于生产环境,还需要考虑工程化问题。

1. 代码结构优化 将关键功能模块化:

  • key_fetcher.py : 负责从Temu获取并解析最新公钥。
  • encryptor.py : 封装RSA加密逻辑。
  • login_client.py : 整合流程,管理会话,处理登录请求和响应。
  • config.py : 存放账号、URL等配置。

2. 错误处理与重试 网络请求和服务器响应都可能出错。代码中必须加入健壮的错误处理和重试机制(如对连接超时、状态码非200等情况进行指数退避重试)。

3. 日志记录 详细的日志对于排查生产环境问题至关重要。记录关键步骤:获取公钥成功/失败、加密开始、登录请求与响应状态码及内容。

4. 密钥与会话管理

  • 公钥应有缓存机制,避免每次登录都重新获取,但需设置合理的过期时间(如1小时)。
  • 登录成功后获得的Cookie或Token需要安全存储(如数据库),并定时检查有效性,实现自动重新登录。

5. 应对变更 平台的前端代码和接口可能会更新。最好能设置一个简单的健康检查:定期用已知正确的账号密码尝试登录,如果连续失败,则触发告警,提示可能需要重新进行逆向分析。

我个人在实际操作中的体会是 ,逆向分析就像解谜,耐心和细致观察比技术炫技更重要。成功登录的那一刻很有成就感,但真正的挑战在于如何让这套逻辑在无人值守的后台服务中稳定运行数周甚至数月。这意味着你的代码不仅要“跑得通”,还要“防得住”各种边界情况和平台悄无声息的升级。建议在核心逻辑稳定后,花更多时间在异常处理、日志和监控上,这往往是业余脚本与生产级工具的分水岭。最后,始终牢记合规底线,技术探索应在法律和平台规则允许的范围内进行。

更多推荐