淘宝登录RSA加密逆向:Python复现password2参数生成全流程
1. 项目概述从登录框到加密黑盒做爬虫或者自动化测试的朋友对淘宝登录这个“拦路虎”应该都不陌生。你兴冲冲地写好了脚本模拟了点击填好了用户名和密码结果一提交服务器告诉你密码错误。你明明输对了为什么问题就出在那个看似简单的密码输入框背后藏着一套复杂的客户端加密逻辑。我们今天要啃的硬骨头就是淘宝登录流程中那个关键的password2参数它是由 RSA 算法加密生成的。这不仅仅是一个“找到加密函数然后调用”的简单逆向。淘宝的登录页面经过多年迭代其 JavaScript 代码经过了混淆、压缩和动态加载password2的生成逻辑被巧妙地隐藏在一堆难以阅读的代码中。我们的目标就是像侦探一样从浏览器纷繁的网络请求和源码中抽丝剥茧定位到 RSA 公钥和加密的核心代码并用 Python 完美复现这一过程。最终你将得到一个可以脱离浏览器环境直接生成有效登录凭证的 Python 脚本。这对于需要处理大量账号登录、进行数据采集或自动化测试的场景来说价值不言而喻。2. 核心思路与技术选型解析2.1 逆向工程的核心路径面对一个经过混淆的 Web 应用盲目地通读所有 JS 代码是效率最低下的方法。我们的核心思路是“由外及内顺藤摸瓜”。2.1.1 网络请求分析是起点一切始于浏览器的开发者工具F12。在淘宝登录页面输入密码点击登录观察网络Network选项卡中发出的请求。你会发现一个关键的 POST 请求其请求参数中就包含了我们的目标password2它是一长串看似随机的字符。同时你很可能还会发现一个publicKey或类似名称的字段或者在一个更早的 GET 请求响应中服务器返回了 RSA 公钥信息。这个公钥是加密的必需品我们必须先拿到它。2.1.2 搜索与断点追踪有了目标参数名password2我们可以在开发者工具的“源代码”Sources选项卡中对所有 JS 文件进行全局搜索。混淆后的代码变量名可能千奇百怪但字符串常量相对容易被保留。搜索”password2“或’password2‘往往能直接定位到设置这个参数的地方。在这里打下断点重新触发登录操作代码执行就会在此暂停。此时调用栈Call Stack窗口是你的最佳导航。它展示了当前断点函数是被谁调用的一层层回溯你就能找到最核心的加密函数入口。这个函数很可能接收明文密码和公钥作为参数输出加密后的密文。2.1.3 算法识别与提取在核心函数内部你会看到诸如new JSEncrypt()、setPublicKey、encrypt等字样这是常见的 JavaScript RSA 库如 jsencrypt的用法。我们的任务就是理清这里面的逻辑公钥以何种格式传入可能是模数n和指数e分开也可能是一个 PEM 格式的字符串明文在加密前是否经过了其他处理比如 URL 编码、Base64 转换等。然后用 Python 的密码学库如rsa或cryptography实现完全相同的逻辑。2.2 为什么选择 Python 进行复现你可能会有疑问既然浏览器里 JS 都能跑为什么还要用 Python 复现环境独立性与自动化Python 脚本可以运行在服务器、无头浏览器或任何没有图形界面的环境中便于集成到自动化流水线或爬虫框架中。性能与资源启动一个完整的浏览器实例即使是无头的消耗的资源远大于一个纯 Python 脚本。对于批量任务性能差异巨大。稳定与可控浏览器环境可能因版本、插件或页面动态加载等因素产生波动。Python 复现的加密逻辑是确定性的只要算法不变输出就恒定更稳定可靠。学习价值这个过程是对非对称加密算法、Web 逆向工程、跨语言编程理解的绝佳实践远比单纯调用一个 Selenium 点击登录按钮要深入得多。注意本教程旨在技术交流与学习帮助你理解现代 Web 应用的安全机制和逆向分析方法。请务必遵守目标网站的服务条款不得将技术用于恶意爬取、攻击或侵犯他人隐私等非法用途。3. 实战逆向定位 password2 的生成逻辑理论说再多不如动手走一遍。我们假设一个典型的淘宝登录页面实际域名和参数可能随时间变化但方法论通用。3.1 第一步捕获关键网络请求打开浏览器开发者工具F12切换到Network选项卡并勾选Preserve log保留日志。访问淘宝登录页输入账号和错误的密码点击登录。在网络请求列表中筛选XHR或Fetch请求寻找登录提交的请求。通常其 URL 会包含login、submit等关键字。点击这个请求查看其Headers和Payload或Request Payload/Form Data。关键发现请求URL可能是https://login.taobao.com/member/login.jhtml?或类似的子域名下的地址。请求参数你会看到loginId用户名、password2加密密码、publicKey等一系列字段。password2的值类似aBcDeFgHiJkL...123456这样的长字符串。同时留意在登录页面加载时或点击登录前是否有一个获取密钥的请求。它可能返回一个 JSON里面包含了publicKey或key、exponent等信息。3.2 第二步在源码中搜索与断点调试切换到Sources选项卡。按CtrlShiftFWindows/Linux或CmdOptFMac打开全局搜索框。搜索关键词”password2“带双引号。在混淆代码中字符串常量比变量名更容易存活下来。在搜索结果中点击进入包含这个字符串的 JS 文件。代码很可能被压缩成一行点击左下角的{}美化代码按钮让其可读。定位加密点 美化后搜索password2找到类似这样的赋值语句data[password2] encryptedPassword; // 或者 obj.password2 encryptFunc(plainPwd, pubKey);在encryptedPassword或encryptFunc这一行左侧的行号上点击设置一个断点。3.3 第三步追踪调用栈与提取逻辑回到登录页再次触发登录操作。代码执行会在你的断点处暂停。观察右侧的Scope或Call Stack面板。Local Scope可以看到当前函数内的变量如plainPwd明文密码、pubKey公钥对象或字符串、encryptedPassword的值。记下它们Call Stack显示函数调用链。点击调用栈中上一层的函数可以跳转到调用当前函数的地方逐步向上回溯直到找到加密的入口函数。核心逻辑提取示例 假设我们最终定位到一个函数function encryptPassword(pwd, key) { var encryptor new JSEncrypt(); encryptor.setPublicKey(key); var encrypted encryptor.encrypt(pwd); return encrypted; }这就非常清晰了使用了 JSEncrypt 库公钥key是字符串形式通常是 PEM 格式以-----BEGIN PUBLIC KEY-----开头直接加密明文密码pwd。更复杂的情况 有时公钥不是直接以 PEM 字符串给出而是服务器返回了模数n一个很长的16进制或Base64字符串和指数e通常是”10001“的16进制0x010001。那么 JS 代码中可能会用这些参数构造一个 RSAKey 对象。类似这样var rsaKey new RSAKey(); rsaKey.setPublic(n, e); // n 是模数e 是指数 var encrypted rsaKey.encrypt(pwd);我们需要记录下n和e的具体值以及它们的格式是16进制字符串还是Base64。实操心得在断点状态下你可以将鼠标悬停在变量上查看其值或在 Console 面板中直接输入变量名回车查看。对于复杂的对象使用JSON.stringify(varName, null, 2)可以漂亮地打印出来方便复制。4. Python 复现 RSA 加密全过程拿到了加密逻辑和密钥信息我们就可以在 Python 中动手了。这里根据上面分析的两种常见情况给出复现方案。4.1 环境准备与依赖安装首先确保你的 Python 环境建议 3.6 以上已经就绪。我们需要安装处理 RSA 加密的库。rsa库和cryptography库都是不错的选择cryptography更强大也更现代。这里以cryptography为例。pip install cryptography4.2 情况一使用 PEM 格式公钥字符串如果逆向发现公钥是完整的 PEM 格式字符串-----BEGIN PUBLIC KEY-----...那么复现非常简单。from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes import base64 # 从网络请求或源码中获取的 PEM 公钥字符串 public_key_pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzXpLZgJYLpFhW... ... 你的实际公钥内容 ... -----END PUBLIC KEY----- # 1. 加载公钥 public_key serialization.load_pem_public_key( public_key_pem.encode(utf-8) ) # 2. 待加密的明文密码 plain_password YourPassword123 # 3. 执行加密 # PKCS1 v1.5 填充是 JSEncrypt 默认使用的对应 OAEP 填充在 cryptography 中是 PKCS1v15 cipher_text public_key.encrypt( plain_password.encode(utf-8), padding.PKCS1v15() # 注意这是关键淘宝登录通常使用 PKCS1v1.5 填充 ) # 4. 将加密后的字节进行 Base64 编码得到最终的 password2 password2 base64.b64encode(cipher_text).decode(utf-8) print(f加密后的 password2: {password2})关键点解析填充方案padding.PKCS1v15()是关键。RSA 加密明文需要填充JSEncrypt 默认使用 PKCS#1 v1.5 填充。如果这里选错比如用了 OAEP加密结果会完全不同导致登录失败。这是逆向复现中最常见的坑之一。编码确保明文密码在加密前转换为字节.encode(utf-8)加密输出也是字节最后按需进行 Base64 编码。4.3 情况二使用模数(n)和指数(e)构造公钥如果服务器返回的是单独的模数n和指数e我们需要用它们来构造公钥。from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding import base64 import binascii # 假设从服务器获取的 n 是 Base64 编码字符串e 是 010001 (16进制字符串) n_base64 AMJ9Td...很长一串...QAB # 你的模数 n e_hex 010001 # 1. 解码并转换参数 # 将 Base64 的 n 解码为字节然后转换为整数 n_bytes base64.b64decode(n_base64) n_int int.from_bytes(n_bytes, byteorderbig) # 大端序 # 将 16 进制的 e 转换为整数 e_int int(e_hex, 16) # 2. 构造 RSA 公钥数字 public_numbers rsa.RSAPublicNumbers(e_int, n_int) # 从数字构造公钥对象需要指定后端 from cryptography.hazmat.backends import default_backend public_key public_numbers.public_key(default_backend()) # 3. 加密明文密码 plain_password YourPassword123 cipher_text public_key.encrypt( plain_password.encode(utf-8), padding.PKCS1v15() # 同样使用 PKCS1v1.5 填充 ) # 4. Base64 编码输出 password2 base64.b64encode(cipher_text).decode(utf-8) print(f加密后的 password2: {password2})注意事项字节序int.from_bytes(n_bytes, byteorderbig)中的byteorderbig大端序是网络传输和许多加密库中的标准。务必与 JS 代码中的处理方式保持一致。参数格式务必确认n和e的原始格式。n可能是 Base64也可能是 16 进制字符串。e通常是”010001“16进制对应十进制 65537这是最常用的 RSA 公钥指数。解码步骤需要根据实际情况调整。4.4 验证复现结果生成password2后如何验证它是否正确本地对比法推荐在浏览器断点处获取到 JS 代码生成的password2值。用你的 Python 脚本使用相同的明文密码和相同的公钥进行计算对比两个password2字符串是否完全一致。这是最直接的验证方式。网络请求替换法使用抓包工具如 Fiddler、Charles或浏览器的开发者工具拦截登录请求。将请求体中的password2参数值替换为你 Python 脚本生成的值然后放行请求。观察服务器返回的响应。如果返回成功或密码错误而非参数错误通常说明加密格式是正确的如果返回明显的加密参数错误则说明复现有误。5. 常见问题、调试技巧与深度避坑指南逆向复现的路上不会一帆风顺下面是我踩过坑后总结的一些核心问题和解决方案。5.1 加密结果不一致的排查清单如果你的 Python 输出和 JS 输出不一致请按以下顺序检查公钥是否一致这是最根本的。确保 Python 代码加载的公钥无论是 PEM 字符串还是 n/e与 JS 代码运行时使用的完全一致。一个字符都不能差包括 PEM 的头部尾部标记和换行符。明文是否一致确保加密前的密码字符串完全一致。注意是否有不可见字符、空格或者 JS 端是否对密码进行了预处理如 Trim 操作。填充方案是否正确99% 的问题出在这里。JSEncrypt 默认使用PKCS#1 v1.5填充。在 Pythoncryptography库中对应的就是padding.PKCS1v15()。如果你错误地使用了padding.OAEP结果必然不同。编码解码是否正确牢记流程明文字符串- UTF-8 字节 - RSA 加密 - 字节 - Base64 编码 - 字符串password2。检查每个环节的输入输出类型。模数(n)和指数(e)的处理如果使用 n/e检查它们的编码转换。n从 Base64 解码后得到的字节转换成整数时字节序byteorder是否正确e的进制转换是否正确5.2 进阶处理动态密钥与登录上下文淘宝等大型网站为了安全登录流程可能更复杂动态公钥每次登录页面加载或每次登录尝试前服务器都可能下发一个新的公钥。这意味着你不能硬编码一个公钥必须在 Python 脚本中模拟首次访问从页面或特定接口中提取最新的公钥。登录令牌Token登录请求可能还需要umidToken、sessionId、cookie等其他上下文参数这些参数来自前序的页面请求。你的 Python 脚本需要模拟一个完整的会话先请求登录页解析 HTML 或后续接口响应获取公钥和 Token再组合密码进行加密提交。滑块验证码如果触发验证码单纯的密码加密复现就无法完成登录了。这属于另一个层面的逆向验证码识别或绕过不在本文讨论范围内。但通常在未触发风控的正常环境下正确的加密请求是可以直接登录的。5.3 Python 代码的健壮性优化错误处理添加try...except块来捕获密钥加载失败、加密失败等异常。日志记录使用logging模块记录关键步骤的信息如获取到的公钥片段、加密前后的值便于离线调试。配置化将公钥获取的 URL、请求头等信息写入配置文件提高脚本的适应性。模拟完整会话使用requests.Session()对象来保持 cookies模拟浏览器行为。import requests import re from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding import base64 import logging logging.basicConfig(levellogging.INFO) session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) def fetch_public_key(): 模拟访问登录页从页面或接口中提取公钥 login_page_url https://login.taobao.com/ resp session.get(login_page_url) # 这里需要根据实际页面结构用正则或解析库提取公钥 # 例如公钥可能在某个 script 标签的变量里或者在一个单独的接口返回中 # 假设我们通过正则从 JS 变量中提取到了 PEM 格式公钥 match re.search(rvar pubKey -----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----;, resp.text, re.S) if match: pub_key_str f-----BEGIN PUBLIC KEY-----\n{match.group(1)}\n-----END PUBLIC KEY----- logging.info(成功提取公钥) return pub_key_str else: logging.error(未能提取公钥) return None def encrypt_password(password, public_key_pem): 使用公钥加密密码 public_key serialization.load_pem_public_key(public_key_pem.encode()) cipher public_key.encrypt(password.encode(), padding.PKCS1v15()) return base64.b64encode(cipher).decode() def login(username, encrypted_pwd): 提交登录请求 login_api https://login.taobao.com/member/login.jhtml payload { loginId: username, password2: encrypted_pwd, # ... 其他必要的参数如 token、umid 等需要从前期请求中获取 } resp session.post(login_api, datapayload) # 检查响应判断登录是否成功 return resp # 主流程 if __name__ __main__: pub_key fetch_public_key() if pub_key: my_password YourSecurePassword pwd2 encrypt_password(my_password, pub_key) login_response login(your_usernameexample.com, pwd2) print(login_response.text)6. 总结与扩展思考通过以上步骤我们完成了一次完整的 Web 登录 RSA 加密逆向与 Python 复现。这个过程的核心在于耐心和细致耐心地分析网络请求细致地对比每一步的数据。当你成功用 Python 生成出与浏览器完全一致的password2时那种成就感是无与伦比的。这个技能的价值不仅限于淘宝。几乎所有采用前端加密的登录系统如许多银行、电商、社交平台其逆向思路都是相通的抓包 - 定位关键参数 - 逆向加密函数 - 用其他语言复现。区别只在于加密算法可能是 RSA、AES、SM系列国密等、混淆的复杂度以及反调试手段的强弱。最后再次强调技术是把双刃剑。掌握逆向分析能力能让你更深刻地理解系统工作原理写出更健壮的自动化程序。但请务必用于合法的学习、测试和授权范围内的自动化任务尊重网站的数据安全和用户隐私这是每一位技术从业者的底线。

相关新闻