1. 项目概述从一次失败的请求说起做爬虫或者数据采集的朋友估计都遇到过美团外卖这个“硬骨头”。你可能兴致勃勃地写好了requests代码模拟了headers甚至把cookies都处理得妥妥当当但一发送请求返回的要么是403 Forbidden要么就是一堆看不懂的加密数据核心信息一个都拿不到。问题的根源往往就出在一个叫做mtgsig的参数上。这个mtgsig可不是普通的token或者sign它是美团外卖API在关键请求尤其是涉及用户、商家、订单等敏感数据时中使用的一套复杂的签名加密算法。你的请求里如果没有携带一个正确生成的mtgsig服务器会直接判定为非法请求拒绝服务。我最初接触时也踩了不少坑发现网上零散的资料要么过时要么语焉不详。所以今天我就把自己逆向分析mtgsig 1.2版本并用Python完整实现的过程从头到尾拆解一遍。这不仅仅是一个参数怎么生成的问题更是一次对现代前端加密和风控策略的深度探索。本文将详细解析mtgsig 1.2的算法构成包括其核心的RSA加密、AES加密以及自定义哈希算法的混合应用并提供可直接运行、用于学习和测试的Python实现代码。无论你是想了解大型互联网公司的风控思路还是需要解决实际的数据采集难题这篇文章都能给你提供清晰的路径和可落地的方案。2. 核心思路与算法架构拆解逆向分析mtgsig不能上来就硬抠代码得先理解它的设计意图。美团外卖作为日活千万级的应用其API安全的核心目标就两个防伪造和防重放。mtgsig算法就是为了同时满足这两个目标而设计的复合型签名。2.1 mtgsig 1.2 的组成与作用通过抓包分析使用Fiddler、Charles或浏览器开发者工具你会发现在请求美团外卖的某些API接口时POST的Form Data或者URL参数里会携带一个名为mtgsig的长字符串。这个字符串看起来像是一段被URLEncode过的、结构化的数据。经过逆向其前端JavaScript代码主要是*.js文件中的混淆代码可以解析出mtgsig 1.2的典型结构。它不是一个单一的加密结果而是一个包含了多种信息的令牌包。一个解码后的mtgsig示例结构如下{ a: 2, b: xxxxxx, c: yyyyyy, d: zzzzzz, e: 1234567890123, f: 1.2, g: aaaaaaaa }每个字段都有其特定的含义a(version_algo) 算法标识。2通常代表当前使用的RSAAES混合算法套件。b(token) 一个关键的令牌通常由服务端下发或前端生成具有时效性是防重放攻击的关键之一。c(encrypted_data)核心加密数据。这是整个签名的“心脏”它里面加密了本次请求真正的“签名”信息。通常使用AES算法加密。d(aes_key_encrypted) 用于解密c的AES密钥但这个密钥本身又被RSA公钥加密了。这是典型的“非对称加密保护对称密钥”模式。e(timestamp) 13位毫秒级时间戳用于校验请求的新鲜度。f(sig_version) 签名版本号这里是1.2。g(signature) 对上述部分或全部字段特别是a,b,e,f等进行某种哈希计算如自定义的MD5或SHA256变种得到的签名用于验证mtgsig令牌包自身的完整性防止被篡改。注意 字段名a,b,c...和具体包含的字段数量可能随版本微调但核心思路RSA加密AES密钥AES加密签名数据是稳定的。逆向时务必以当前抓包分析为准。2.2 算法流程与密钥体系理解了结构我们再来看mtgsig的生成流程它清晰地体现了一个分层加密的思想准备原材料 首先需要生成一个本次请求的唯一签名我们暂称为inner_sig。这个inner_sig通常由请求的URL、GET/POST参数、Cookie中的某些字段如userId、时间戳等按照特定规则拼接后再进行一次哈希如MD5计算得到。这是请求内容本身的“指纹”。对称加密核心数据 将上一步得到的inner_sig连同其他一些校验信息可能包括token、uuid等组装成一个字符串或JSON对象。然后使用一个随机生成的AES-128-CBC密钥aes_key和随机初始向量IV对这个数据包进行加密得到字段cencrypted_data。非对称加密保护密钥 随机生成的aes_key和IV本身是明文的不能直接传输。美团前端会使用一个固定的RSA公钥对aes_key有时连同IV进行加密得到字段daes_key_encrypted。这个RSA公钥通常硬编码在JavaScript文件中。组装与二次签名 将算法标识a、令牌b、加密数据c、加密密钥d、时间戳e、版本号f等字段以keyvalue的形式按固定顺序拼接成一个字符串。然后对这个字符串使用一个自定义的哈希算法可能是在标准MD5基础上做了盐值混淆或循环处理进行计算得到最终的完整性签名g。编码与发送 将整个JSON对象{a:..., b:..., ..., g:...}进行JSON序列化然后进行URL编码或直接作为字符串作为mtgsig参数附带到请求中。服务器端验证过程则相反先用同样的规则计算g是否匹配验证令牌包完整然后用私钥解密d得到aes_key再用aes_key解密c得到inner_sig最后用请求内容重新计算inner_sig进行比对。实操心得 这里最关键的三个点是1) 用于计算inner_sig的参数拼接规则2) 前端使用的固定RSA公钥3) 计算最终签名g的自定义哈希算法。这三者都需要通过静态分析JS代码和动态调试来精准定位。3. 逆向工程关键步骤与工具理论清楚了接下来就是实战。逆向mtgsig生成逻辑主要依赖于对前端JavaScript代码的分析。这个过程需要耐心和一定的技巧。3.1 环境准备与抓包定位工欲善其事必先利其器。你需要准备好以下工具抓包工具Charles或Fiddler。配置好SSL代理以便捕获HTTPS请求。Chrome/Edge开发者工具的网络面板Network也足够用。浏览器 推荐使用Chrome或Edge其开发者工具功能强大。代码调试工具 浏览器自带的Sources面板和Console面板是主战场。Firefox的调试器也不错。格式化与搜索工具 浏览器能自动格式化混淆的JS但一个能全局搜索的编辑器如VSCode在分析大文件时很有帮助。首先打开美团外卖网页版或H5页面进行登录、搜索商家、查看商品等操作同时在开发者工具的Network面板中筛选XHR/Fetch请求。找到那些返回数据丰富但请求参数中包含mtgsig的接口例如商家列表、商品列表、订单详情接口。记录下完整的请求URL、Headers和Form Data。3.2 定位加密入口与关键函数这是逆向中最具技巧性的一步。由于代码被严重混淆变量名变成a,b,c逻辑被分割和包裹直接阅读几乎不可能。我们采用“由外及内动态跟踪”的方法。搜索关键字 在开发者工具的Sources面板中对所有JS文件进行全局搜索CtrlShiftF。搜索关键词可以是mtgsig、encrypt、rsa、aes、sign、1.2等。通常能在某个巨大的vendor.js或app.js中找到相关代码片段。设置XHR断点 在Sources面板的XHR/Fetch Breakpoints中添加一个包含目标接口URL关键词的断点。当浏览器发起对该接口的请求时执行流会自动暂停。调用栈分析 断点触发后查看Call Stack调用栈。调用栈会显示从发起请求到断点处经过的所有函数。从栈顶往下找寻找那些看起来与业务逻辑相关函数名可能包含request,ajax,sign等或者代码结构相对清晰的函数点击进入。追踪参数生成 进入疑似函数后重点观察在发送请求前mtgsig参数是如何被赋值的。通常会有一个函数调用其返回值被赋给了mtgsig。在这个函数上打上断点刷新页面或重新触发请求跟进这个函数。逐步执行与观察 使用F11步入逐步执行代码同时观察Scope作用域中变量值的变化。你的目标是找到生成inner_sig的拼接逻辑、AES密钥的生成与加密、RSA加密的调用点、以及最终组装和计算签名g的地方。避坑指南 混淆代码中会有大量条件判断、循环和异常处理来干扰调试。遇到非常复杂的逻辑块时可以尝试在Console中直接执行其中的一小段代码看输出结果帮助理解。另外注意Promise和异步回调调试时可能需要切换到对应的异步上下文。3.3 提取核心算法与密钥一旦跟踪到核心函数下一步就是将其逻辑“翻译”成Python。提取RSA公钥 在代码中找到RSA加密的地方通常会有一个很长的字符串Base64编码或16进制表示这就是公钥。将其完整复制出来。注意观察其格式可能是PKCS#1或PKCS#8。在Python中我们需要将其转换为PEM格式的字符串以便rsa或Crypto库加载。# 示例从JS中提取的可能是这样的变量 # var rsaPublicKey MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1W1kH...分析AES模式与填充 找到AES加密的函数调用。确认其使用的模式通常是CBC、密钥长度128位、填充方式PKCS7。同时找到生成随机IV初始化向量的代码。还原哈希算法 计算签名g的哈希函数是重点。它可能看起来像MD5但内部可能进行了多次循环、加盐salt或字符变换。你需要仔细分析该函数的每一步操作确保在Python中能完全复现。有时它可能是一个自定义的CRC32或者对SHA256的截断处理。确定参数拼接顺序 找到组装mtgsig字典以及计算签名g前拼接字符串的代码。确认各个字段a,b,c...的拼接顺序和分隔符通常是或|。这个过程需要反复对照JS执行结果和你的Python代码输出使用相同的输入确保每一步的中间结果都完全一致。4. Python实现mtgsig 1.2生成器经过艰苦的逆向分析我们拿到了所有必要的算法细节。现在用Python将它们实现出来。这里假设我们已经分析出以下信息请注意以下密钥和盐值为示例实际需自行逆向获取RSA公钥 一个PKCS#8格式的PEM字符串。AES模式AES-128-CBC填充为PKCS7。哈希算法gMD5(拼接字符串 “固定盐值”)。inner_sig算法MD5(参数1值1参数2值2...tokenxxxx)参数按字母序排序。我们将使用pycryptodome库来进行加密操作。首先安装依赖pip install pycryptodome。4.1 基础工具函数实现我们先实现一些通用的加密函数。import json import time import random import base64 import hashlib from urllib.parse import quote_plus from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.PublicKey import RSA from Crypto.Util.Padding import pad, unpad from Crypto.Hash import MD5 class MTGSigGenerator: def __init__(self, rsa_public_key_pem, hash_salt): 初始化生成器 :param rsa_public_key_pem: RSA公钥(PEM格式字符串) :param hash_salt: 计算最终签名g时使用的盐值 self.rsa_public_key RSA.import_key(rsa_public_key_pem) self.hash_salt hash_salt # 示例固定token实际中可能需要从网络请求或本地存储获取 self.token your_dynamic_token_here def _random_string(self, length16): 生成指定长度的随机字符串用于AES密钥和IV chars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 return .join(random.choice(chars) for _ in range(length)) def _aes_encrypt(self, plaintext, key, iv): AES-128-CBC PKCS7加密 cipher AES.new(key.encode(utf-8), AES.MODE_CBC, iv.encode(utf-8)) ciphertext cipher.encrypt(pad(plaintext.encode(utf-8), AES.block_size)) return base64.b64encode(ciphertext).decode(utf-8) def _rsa_encrypt(self, plaintext): RSA加密使用PKCS#1 v1.5填充 cipher PKCS1_v1_5.new(self.rsa_public_key) # RSA加密有长度限制需要分段加密但AES密钥较短一般一次即可 ciphertext cipher.encrypt(plaintext.encode(utf-8)) return base64.b64encode(ciphertext).decode(utf-8)4.2 核心签名生成流程接下来是核心的generate方法它模拟了前端生成mtgsig的全过程。def generate(self, url_path, query_params, body_paramsNone): 生成mtgsig 1.2 :param url_path: 请求路径如 /api/v1/poi/filter :param query_params: GET查询参数字典 :param body_params: POST请求体参数字典 :return: mtgsig参数字符串 # 1. 生成inner_sig (请求内容签名) inner_sig self._generate_inner_sig(url_path, query_params, body_params) # 2. 准备AES加密的数据包 (模拟前端结构) # 注意这里的数据结构需要根据逆向结果调整 data_to_encrypt { sig: inner_sig, token: self.token, timestamp: int(time.time() * 1000), # 可能还有其他字段如uuid, deviceId等 } encrypted_data_str json.dumps(data_to_encrypt, separators(,, :), ensure_asciiFalse) # 3. 生成随机的AES密钥和IV并加密数据 aes_key self._random_string(16) # AES-128 密钥为16字节 aes_iv self._random_string(16) # CBC模式需要16字节IV encrypted_data self._aes_encrypt(encrypted_data_str, aes_key, aes_iv) # 对应字段 c # 4. 用RSA公钥加密AES密钥 (可能同时加密IV根据逆向结果定) # 假设前端将 key 和 iv 用某个分隔符拼接后一起加密 key_iv_combo f{aes_key}|{aes_iv} encrypted_key self._rsa_encrypt(key_iv_combo) # 对应字段 d # 5. 组装mtgsig字典 timestamp int(time.time() * 1000) mtgsig_dict { a: 2, # 算法标识 b: self.token, c: encrypted_data, d: encrypted_key, e: timestamp, f: 1.2, # 版本号 # g 字段最后计算 } # 6. 计算最终签名 g # 根据逆向结果确定拼接顺序例如: a2btokenc...d...etimestampf1.2 # 注意字段c和d已经是Base64字符串可能包含特殊字符需要确认前端是否做URL编码 sign_string fa{mtgsig_dict[a]}b{mtgsig_dict[b]}c{mtgsig_dict[c]}d{mtgsig_dict[d]}e{mtgsig_dict[e]}f{mtgsig_dict[f]} sign_string self.hash_salt # 加上盐值 # 计算MD5 (可能是自定义哈希这里用标准MD5示例) signature_g hashlib.md5(sign_string.encode(utf-8)).hexdigest() mtgsig_dict[g] signature_g # 7. 序列化并URL编码 (有时不需要编码直接发送JSON字符串) mtgsig_json_str json.dumps(mtgsig_dict, separators(,, :), ensure_asciiFalse) # 根据抓包观察决定是否进行URL编码 # mtgsig_final quote_plus(mtgsig_json_str) mtgsig_final mtgsig_json_str return mtgsig_final def _generate_inner_sig(self, url_path, query_params, body_params): 生成内部签名规则需要根据逆向精确还原 # 这是一个示例规则实际非常复杂 sign_dict {} # 合并参数通常包括URL路径、查询参数、请求体参数、固定token等 sign_dict[url] url_path if query_params: sign_dict.update(query_params) if body_params: sign_dict.update(body_params) sign_dict[token] self.token # 按Key的字母顺序排序 sorted_items sorted(sign_dict.items(), keylambda x: x[0]) # 拼接成 key1value1key2value2 的形式 sign_string .join([f{k}{v} for k, v in sorted_items]) # 计算MD5 inner_sig hashlib.md5(sign_string.encode(utf-8)).hexdigest() return inner_sig4.3 使用示例与测试最后我们写一个简单的示例来使用这个生成器并与浏览器生成的mtgsig进行对比测试。# 示例用法 if __name__ __main__: # !!! 警告以下密钥和盐值为虚构示例不可直接使用 !!! # 你必须通过逆向分析获取真实的公钥和盐值 SAMPLE_RSA_PUBLIC_KEY -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx7H1... ... (你的真实RSA公钥) -----END PUBLIC KEY----- SAMPLE_HASH_SALT saltyour_salt_value generator MTGSigGenerator(SAMPLE_RSA_PUBLIC_KEY, SAMPLE_HASH_SALT) # 模拟一个请求 url /api/poi/filter query {lat: 39.90469, lng: 116.40717, page: 1} body {category: 美食} mtgsig generator.generate(url, query, body) print(生成的mtgsig参数:) print(mtgsig) print(\n解码后的结构:) print(json.loads(mtgsig))关键注意事项 上面的代码是一个高度简化的框架展示了核心逻辑。其中最重要的_generate_inner_sig方法、data_to_encrypt的结构、签名g的拼接规则和哈希算法都必须根据你逆向分析美团外卖具体接口的实际结果来填充和修改。直接运行上述代码是无法生成有效mtgsig的。5. 常见问题、调试技巧与对抗升级即使算法完全正确在实际使用中也可能遇到各种问题。这里分享一些调试经验和应对策略。5.1 问题排查清单当你实现的mtgsig不被服务器接受时可以按照以下顺序排查问题现象可能原因排查方法返回403或签名错误inner_sig计算错误1. 核对参与签名的所有参数是否齐全特别是token,uuid,deviceId等。2. 确认参数拼接顺序、大小写、分隔符。3. 使用浏览器调试在计算inner_sig的函数处断点记录所有输入与你Python代码的输入逐字对比。返回mtgsig解析失败mtgsig令牌包结构或编码错误1. 检查mtgsig字典的字段名(a,b...)和类型(数字/字符串)是否正确。2. 检查c和d字段的Base64编码是否正确是否包含换行符。3. 检查最终输出的mtgsig是JSON字符串还是URL编码后的字符串需与抓包请求严格一致。返回密钥解密失败RSA公钥不正确或加密格式不对1. 确认提取的RSA公钥完整无误格式是Python的RSA.import_key能识别的PEM。2. 确认加密模式是PKCS1_v1_5还是OAEP需要与前端一致。3. 确认加密的内容是aes_key还是aes_keyiv以及拼接分隔符。返回数据解密失败AES密钥/IV错误或加密数据格式不对1. 用RSA私钥如果你有解密d字段看得到的aes_key和iv是否与你生成的一致。2. 确认AES模式(CBC)、填充(PKCS7)、数据块大小(16)是否正确。3. 确认c字段解密后的JSON结构是否符合服务器预期。请求偶尔成功多数失败token或时间戳失效1.token可能有过期时间需要定期从某个接口或Cookie中获取。2. 检查服务器时间差timestamp是否在服务器可接受的时间窗口内如±5分钟。5.2 动态参数获取与维护mtgsig算法中的一些参数是动态的这增加了自动化难度token(b字段) 通常来自登录后的Cookie如_token或某个初始化接口的响应。需要维护会话(session)来保持token的有效性。uuid/deviceId 这些设备标识符可能参与inner_sig的计算。它们通常在页面加载时由前端生成并存储在本地如localStorage需要在发起请求前获取。RSA公钥与哈希盐值 相对固定但并非永久不变。美团可能会不定期更新前端JS文件导致公钥和算法微调。需要建立监控机制当大量请求失败时重新抓取和分析JS文件。5.3 对抗风控升级大型平台的风控是持续对抗的过程。除了mtgsig美团外卖还可能部署了其他风控措施请求频率限制 过于频繁的请求会触发封禁。需要合理设置请求间隔使用代理IP池。行为指纹 通过JavaScript收集浏览器指纹Canvas,WebGL,Fonts等。纯Python请求缺乏这些指纹可能被识别。更高级的模拟需要使用Puppeteer、Selenium等浏览器自动化工具但效率较低。代码混淆与动态更新 前端JS混淆强度会升级甚至关键逻辑可能通过WebAssembly实现或每次动态加载。这要求逆向者具备更强的代码分析和动态调试能力。mtgsig版本迭代1.2之后可能会有1.3、2.0等版本算法可能完全改变。需要关注请求中f字段版本号的变化。个人经验分享 对于长期稳定的数据采集需求最稳妥的方式不是追求完全逆向最新的加密而是维护一个可自动更新的“算法库”。思路是将核心的逆向和提取逻辑如定位公钥、解析哈希函数也写成脚本定期自动抓取目标页面最新的主JS文件通过特征匹配和简单的静态分析自动提取出最新的公钥和盐值。这样当对方更新时你的系统能在短时间内自动适应只需人工介入验证即可极大提升了抗风险能力。当然这需要更深入的工程化设计。逆向工程就像一场猫鼠游戏充满了挑战和乐趣。理解mtgsig这样的加密算法不仅能解决具体的技术问题更能让你深刻体会到现代Web安全技术的精妙之处。希望这篇详细的解析和实现指南能为你打开一扇门。记住关键在于耐心、细致的分析和不断的实践验证。
逆向美团外卖mtgsig 1.2签名算法:Python实现与风控对抗实战
发布时间:2026/7/16 4:14:13
1. 项目概述从一次失败的请求说起做爬虫或者数据采集的朋友估计都遇到过美团外卖这个“硬骨头”。你可能兴致勃勃地写好了requests代码模拟了headers甚至把cookies都处理得妥妥当当但一发送请求返回的要么是403 Forbidden要么就是一堆看不懂的加密数据核心信息一个都拿不到。问题的根源往往就出在一个叫做mtgsig的参数上。这个mtgsig可不是普通的token或者sign它是美团外卖API在关键请求尤其是涉及用户、商家、订单等敏感数据时中使用的一套复杂的签名加密算法。你的请求里如果没有携带一个正确生成的mtgsig服务器会直接判定为非法请求拒绝服务。我最初接触时也踩了不少坑发现网上零散的资料要么过时要么语焉不详。所以今天我就把自己逆向分析mtgsig 1.2版本并用Python完整实现的过程从头到尾拆解一遍。这不仅仅是一个参数怎么生成的问题更是一次对现代前端加密和风控策略的深度探索。本文将详细解析mtgsig 1.2的算法构成包括其核心的RSA加密、AES加密以及自定义哈希算法的混合应用并提供可直接运行、用于学习和测试的Python实现代码。无论你是想了解大型互联网公司的风控思路还是需要解决实际的数据采集难题这篇文章都能给你提供清晰的路径和可落地的方案。2. 核心思路与算法架构拆解逆向分析mtgsig不能上来就硬抠代码得先理解它的设计意图。美团外卖作为日活千万级的应用其API安全的核心目标就两个防伪造和防重放。mtgsig算法就是为了同时满足这两个目标而设计的复合型签名。2.1 mtgsig 1.2 的组成与作用通过抓包分析使用Fiddler、Charles或浏览器开发者工具你会发现在请求美团外卖的某些API接口时POST的Form Data或者URL参数里会携带一个名为mtgsig的长字符串。这个字符串看起来像是一段被URLEncode过的、结构化的数据。经过逆向其前端JavaScript代码主要是*.js文件中的混淆代码可以解析出mtgsig 1.2的典型结构。它不是一个单一的加密结果而是一个包含了多种信息的令牌包。一个解码后的mtgsig示例结构如下{ a: 2, b: xxxxxx, c: yyyyyy, d: zzzzzz, e: 1234567890123, f: 1.2, g: aaaaaaaa }每个字段都有其特定的含义a(version_algo) 算法标识。2通常代表当前使用的RSAAES混合算法套件。b(token) 一个关键的令牌通常由服务端下发或前端生成具有时效性是防重放攻击的关键之一。c(encrypted_data)核心加密数据。这是整个签名的“心脏”它里面加密了本次请求真正的“签名”信息。通常使用AES算法加密。d(aes_key_encrypted) 用于解密c的AES密钥但这个密钥本身又被RSA公钥加密了。这是典型的“非对称加密保护对称密钥”模式。e(timestamp) 13位毫秒级时间戳用于校验请求的新鲜度。f(sig_version) 签名版本号这里是1.2。g(signature) 对上述部分或全部字段特别是a,b,e,f等进行某种哈希计算如自定义的MD5或SHA256变种得到的签名用于验证mtgsig令牌包自身的完整性防止被篡改。注意 字段名a,b,c...和具体包含的字段数量可能随版本微调但核心思路RSA加密AES密钥AES加密签名数据是稳定的。逆向时务必以当前抓包分析为准。2.2 算法流程与密钥体系理解了结构我们再来看mtgsig的生成流程它清晰地体现了一个分层加密的思想准备原材料 首先需要生成一个本次请求的唯一签名我们暂称为inner_sig。这个inner_sig通常由请求的URL、GET/POST参数、Cookie中的某些字段如userId、时间戳等按照特定规则拼接后再进行一次哈希如MD5计算得到。这是请求内容本身的“指纹”。对称加密核心数据 将上一步得到的inner_sig连同其他一些校验信息可能包括token、uuid等组装成一个字符串或JSON对象。然后使用一个随机生成的AES-128-CBC密钥aes_key和随机初始向量IV对这个数据包进行加密得到字段cencrypted_data。非对称加密保护密钥 随机生成的aes_key和IV本身是明文的不能直接传输。美团前端会使用一个固定的RSA公钥对aes_key有时连同IV进行加密得到字段daes_key_encrypted。这个RSA公钥通常硬编码在JavaScript文件中。组装与二次签名 将算法标识a、令牌b、加密数据c、加密密钥d、时间戳e、版本号f等字段以keyvalue的形式按固定顺序拼接成一个字符串。然后对这个字符串使用一个自定义的哈希算法可能是在标准MD5基础上做了盐值混淆或循环处理进行计算得到最终的完整性签名g。编码与发送 将整个JSON对象{a:..., b:..., ..., g:...}进行JSON序列化然后进行URL编码或直接作为字符串作为mtgsig参数附带到请求中。服务器端验证过程则相反先用同样的规则计算g是否匹配验证令牌包完整然后用私钥解密d得到aes_key再用aes_key解密c得到inner_sig最后用请求内容重新计算inner_sig进行比对。实操心得 这里最关键的三个点是1) 用于计算inner_sig的参数拼接规则2) 前端使用的固定RSA公钥3) 计算最终签名g的自定义哈希算法。这三者都需要通过静态分析JS代码和动态调试来精准定位。3. 逆向工程关键步骤与工具理论清楚了接下来就是实战。逆向mtgsig生成逻辑主要依赖于对前端JavaScript代码的分析。这个过程需要耐心和一定的技巧。3.1 环境准备与抓包定位工欲善其事必先利其器。你需要准备好以下工具抓包工具Charles或Fiddler。配置好SSL代理以便捕获HTTPS请求。Chrome/Edge开发者工具的网络面板Network也足够用。浏览器 推荐使用Chrome或Edge其开发者工具功能强大。代码调试工具 浏览器自带的Sources面板和Console面板是主战场。Firefox的调试器也不错。格式化与搜索工具 浏览器能自动格式化混淆的JS但一个能全局搜索的编辑器如VSCode在分析大文件时很有帮助。首先打开美团外卖网页版或H5页面进行登录、搜索商家、查看商品等操作同时在开发者工具的Network面板中筛选XHR/Fetch请求。找到那些返回数据丰富但请求参数中包含mtgsig的接口例如商家列表、商品列表、订单详情接口。记录下完整的请求URL、Headers和Form Data。3.2 定位加密入口与关键函数这是逆向中最具技巧性的一步。由于代码被严重混淆变量名变成a,b,c逻辑被分割和包裹直接阅读几乎不可能。我们采用“由外及内动态跟踪”的方法。搜索关键字 在开发者工具的Sources面板中对所有JS文件进行全局搜索CtrlShiftF。搜索关键词可以是mtgsig、encrypt、rsa、aes、sign、1.2等。通常能在某个巨大的vendor.js或app.js中找到相关代码片段。设置XHR断点 在Sources面板的XHR/Fetch Breakpoints中添加一个包含目标接口URL关键词的断点。当浏览器发起对该接口的请求时执行流会自动暂停。调用栈分析 断点触发后查看Call Stack调用栈。调用栈会显示从发起请求到断点处经过的所有函数。从栈顶往下找寻找那些看起来与业务逻辑相关函数名可能包含request,ajax,sign等或者代码结构相对清晰的函数点击进入。追踪参数生成 进入疑似函数后重点观察在发送请求前mtgsig参数是如何被赋值的。通常会有一个函数调用其返回值被赋给了mtgsig。在这个函数上打上断点刷新页面或重新触发请求跟进这个函数。逐步执行与观察 使用F11步入逐步执行代码同时观察Scope作用域中变量值的变化。你的目标是找到生成inner_sig的拼接逻辑、AES密钥的生成与加密、RSA加密的调用点、以及最终组装和计算签名g的地方。避坑指南 混淆代码中会有大量条件判断、循环和异常处理来干扰调试。遇到非常复杂的逻辑块时可以尝试在Console中直接执行其中的一小段代码看输出结果帮助理解。另外注意Promise和异步回调调试时可能需要切换到对应的异步上下文。3.3 提取核心算法与密钥一旦跟踪到核心函数下一步就是将其逻辑“翻译”成Python。提取RSA公钥 在代码中找到RSA加密的地方通常会有一个很长的字符串Base64编码或16进制表示这就是公钥。将其完整复制出来。注意观察其格式可能是PKCS#1或PKCS#8。在Python中我们需要将其转换为PEM格式的字符串以便rsa或Crypto库加载。# 示例从JS中提取的可能是这样的变量 # var rsaPublicKey MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1W1kH...分析AES模式与填充 找到AES加密的函数调用。确认其使用的模式通常是CBC、密钥长度128位、填充方式PKCS7。同时找到生成随机IV初始化向量的代码。还原哈希算法 计算签名g的哈希函数是重点。它可能看起来像MD5但内部可能进行了多次循环、加盐salt或字符变换。你需要仔细分析该函数的每一步操作确保在Python中能完全复现。有时它可能是一个自定义的CRC32或者对SHA256的截断处理。确定参数拼接顺序 找到组装mtgsig字典以及计算签名g前拼接字符串的代码。确认各个字段a,b,c...的拼接顺序和分隔符通常是或|。这个过程需要反复对照JS执行结果和你的Python代码输出使用相同的输入确保每一步的中间结果都完全一致。4. Python实现mtgsig 1.2生成器经过艰苦的逆向分析我们拿到了所有必要的算法细节。现在用Python将它们实现出来。这里假设我们已经分析出以下信息请注意以下密钥和盐值为示例实际需自行逆向获取RSA公钥 一个PKCS#8格式的PEM字符串。AES模式AES-128-CBC填充为PKCS7。哈希算法gMD5(拼接字符串 “固定盐值”)。inner_sig算法MD5(参数1值1参数2值2...tokenxxxx)参数按字母序排序。我们将使用pycryptodome库来进行加密操作。首先安装依赖pip install pycryptodome。4.1 基础工具函数实现我们先实现一些通用的加密函数。import json import time import random import base64 import hashlib from urllib.parse import quote_plus from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.PublicKey import RSA from Crypto.Util.Padding import pad, unpad from Crypto.Hash import MD5 class MTGSigGenerator: def __init__(self, rsa_public_key_pem, hash_salt): 初始化生成器 :param rsa_public_key_pem: RSA公钥(PEM格式字符串) :param hash_salt: 计算最终签名g时使用的盐值 self.rsa_public_key RSA.import_key(rsa_public_key_pem) self.hash_salt hash_salt # 示例固定token实际中可能需要从网络请求或本地存储获取 self.token your_dynamic_token_here def _random_string(self, length16): 生成指定长度的随机字符串用于AES密钥和IV chars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 return .join(random.choice(chars) for _ in range(length)) def _aes_encrypt(self, plaintext, key, iv): AES-128-CBC PKCS7加密 cipher AES.new(key.encode(utf-8), AES.MODE_CBC, iv.encode(utf-8)) ciphertext cipher.encrypt(pad(plaintext.encode(utf-8), AES.block_size)) return base64.b64encode(ciphertext).decode(utf-8) def _rsa_encrypt(self, plaintext): RSA加密使用PKCS#1 v1.5填充 cipher PKCS1_v1_5.new(self.rsa_public_key) # RSA加密有长度限制需要分段加密但AES密钥较短一般一次即可 ciphertext cipher.encrypt(plaintext.encode(utf-8)) return base64.b64encode(ciphertext).decode(utf-8)4.2 核心签名生成流程接下来是核心的generate方法它模拟了前端生成mtgsig的全过程。def generate(self, url_path, query_params, body_paramsNone): 生成mtgsig 1.2 :param url_path: 请求路径如 /api/v1/poi/filter :param query_params: GET查询参数字典 :param body_params: POST请求体参数字典 :return: mtgsig参数字符串 # 1. 生成inner_sig (请求内容签名) inner_sig self._generate_inner_sig(url_path, query_params, body_params) # 2. 准备AES加密的数据包 (模拟前端结构) # 注意这里的数据结构需要根据逆向结果调整 data_to_encrypt { sig: inner_sig, token: self.token, timestamp: int(time.time() * 1000), # 可能还有其他字段如uuid, deviceId等 } encrypted_data_str json.dumps(data_to_encrypt, separators(,, :), ensure_asciiFalse) # 3. 生成随机的AES密钥和IV并加密数据 aes_key self._random_string(16) # AES-128 密钥为16字节 aes_iv self._random_string(16) # CBC模式需要16字节IV encrypted_data self._aes_encrypt(encrypted_data_str, aes_key, aes_iv) # 对应字段 c # 4. 用RSA公钥加密AES密钥 (可能同时加密IV根据逆向结果定) # 假设前端将 key 和 iv 用某个分隔符拼接后一起加密 key_iv_combo f{aes_key}|{aes_iv} encrypted_key self._rsa_encrypt(key_iv_combo) # 对应字段 d # 5. 组装mtgsig字典 timestamp int(time.time() * 1000) mtgsig_dict { a: 2, # 算法标识 b: self.token, c: encrypted_data, d: encrypted_key, e: timestamp, f: 1.2, # 版本号 # g 字段最后计算 } # 6. 计算最终签名 g # 根据逆向结果确定拼接顺序例如: a2btokenc...d...etimestampf1.2 # 注意字段c和d已经是Base64字符串可能包含特殊字符需要确认前端是否做URL编码 sign_string fa{mtgsig_dict[a]}b{mtgsig_dict[b]}c{mtgsig_dict[c]}d{mtgsig_dict[d]}e{mtgsig_dict[e]}f{mtgsig_dict[f]} sign_string self.hash_salt # 加上盐值 # 计算MD5 (可能是自定义哈希这里用标准MD5示例) signature_g hashlib.md5(sign_string.encode(utf-8)).hexdigest() mtgsig_dict[g] signature_g # 7. 序列化并URL编码 (有时不需要编码直接发送JSON字符串) mtgsig_json_str json.dumps(mtgsig_dict, separators(,, :), ensure_asciiFalse) # 根据抓包观察决定是否进行URL编码 # mtgsig_final quote_plus(mtgsig_json_str) mtgsig_final mtgsig_json_str return mtgsig_final def _generate_inner_sig(self, url_path, query_params, body_params): 生成内部签名规则需要根据逆向精确还原 # 这是一个示例规则实际非常复杂 sign_dict {} # 合并参数通常包括URL路径、查询参数、请求体参数、固定token等 sign_dict[url] url_path if query_params: sign_dict.update(query_params) if body_params: sign_dict.update(body_params) sign_dict[token] self.token # 按Key的字母顺序排序 sorted_items sorted(sign_dict.items(), keylambda x: x[0]) # 拼接成 key1value1key2value2 的形式 sign_string .join([f{k}{v} for k, v in sorted_items]) # 计算MD5 inner_sig hashlib.md5(sign_string.encode(utf-8)).hexdigest() return inner_sig4.3 使用示例与测试最后我们写一个简单的示例来使用这个生成器并与浏览器生成的mtgsig进行对比测试。# 示例用法 if __name__ __main__: # !!! 警告以下密钥和盐值为虚构示例不可直接使用 !!! # 你必须通过逆向分析获取真实的公钥和盐值 SAMPLE_RSA_PUBLIC_KEY -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx7H1... ... (你的真实RSA公钥) -----END PUBLIC KEY----- SAMPLE_HASH_SALT saltyour_salt_value generator MTGSigGenerator(SAMPLE_RSA_PUBLIC_KEY, SAMPLE_HASH_SALT) # 模拟一个请求 url /api/poi/filter query {lat: 39.90469, lng: 116.40717, page: 1} body {category: 美食} mtgsig generator.generate(url, query, body) print(生成的mtgsig参数:) print(mtgsig) print(\n解码后的结构:) print(json.loads(mtgsig))关键注意事项 上面的代码是一个高度简化的框架展示了核心逻辑。其中最重要的_generate_inner_sig方法、data_to_encrypt的结构、签名g的拼接规则和哈希算法都必须根据你逆向分析美团外卖具体接口的实际结果来填充和修改。直接运行上述代码是无法生成有效mtgsig的。5. 常见问题、调试技巧与对抗升级即使算法完全正确在实际使用中也可能遇到各种问题。这里分享一些调试经验和应对策略。5.1 问题排查清单当你实现的mtgsig不被服务器接受时可以按照以下顺序排查问题现象可能原因排查方法返回403或签名错误inner_sig计算错误1. 核对参与签名的所有参数是否齐全特别是token,uuid,deviceId等。2. 确认参数拼接顺序、大小写、分隔符。3. 使用浏览器调试在计算inner_sig的函数处断点记录所有输入与你Python代码的输入逐字对比。返回mtgsig解析失败mtgsig令牌包结构或编码错误1. 检查mtgsig字典的字段名(a,b...)和类型(数字/字符串)是否正确。2. 检查c和d字段的Base64编码是否正确是否包含换行符。3. 检查最终输出的mtgsig是JSON字符串还是URL编码后的字符串需与抓包请求严格一致。返回密钥解密失败RSA公钥不正确或加密格式不对1. 确认提取的RSA公钥完整无误格式是Python的RSA.import_key能识别的PEM。2. 确认加密模式是PKCS1_v1_5还是OAEP需要与前端一致。3. 确认加密的内容是aes_key还是aes_keyiv以及拼接分隔符。返回数据解密失败AES密钥/IV错误或加密数据格式不对1. 用RSA私钥如果你有解密d字段看得到的aes_key和iv是否与你生成的一致。2. 确认AES模式(CBC)、填充(PKCS7)、数据块大小(16)是否正确。3. 确认c字段解密后的JSON结构是否符合服务器预期。请求偶尔成功多数失败token或时间戳失效1.token可能有过期时间需要定期从某个接口或Cookie中获取。2. 检查服务器时间差timestamp是否在服务器可接受的时间窗口内如±5分钟。5.2 动态参数获取与维护mtgsig算法中的一些参数是动态的这增加了自动化难度token(b字段) 通常来自登录后的Cookie如_token或某个初始化接口的响应。需要维护会话(session)来保持token的有效性。uuid/deviceId 这些设备标识符可能参与inner_sig的计算。它们通常在页面加载时由前端生成并存储在本地如localStorage需要在发起请求前获取。RSA公钥与哈希盐值 相对固定但并非永久不变。美团可能会不定期更新前端JS文件导致公钥和算法微调。需要建立监控机制当大量请求失败时重新抓取和分析JS文件。5.3 对抗风控升级大型平台的风控是持续对抗的过程。除了mtgsig美团外卖还可能部署了其他风控措施请求频率限制 过于频繁的请求会触发封禁。需要合理设置请求间隔使用代理IP池。行为指纹 通过JavaScript收集浏览器指纹Canvas,WebGL,Fonts等。纯Python请求缺乏这些指纹可能被识别。更高级的模拟需要使用Puppeteer、Selenium等浏览器自动化工具但效率较低。代码混淆与动态更新 前端JS混淆强度会升级甚至关键逻辑可能通过WebAssembly实现或每次动态加载。这要求逆向者具备更强的代码分析和动态调试能力。mtgsig版本迭代1.2之后可能会有1.3、2.0等版本算法可能完全改变。需要关注请求中f字段版本号的变化。个人经验分享 对于长期稳定的数据采集需求最稳妥的方式不是追求完全逆向最新的加密而是维护一个可自动更新的“算法库”。思路是将核心的逆向和提取逻辑如定位公钥、解析哈希函数也写成脚本定期自动抓取目标页面最新的主JS文件通过特征匹配和简单的静态分析自动提取出最新的公钥和盐值。这样当对方更新时你的系统能在短时间内自动适应只需人工介入验证即可极大提升了抗风险能力。当然这需要更深入的工程化设计。逆向工程就像一场猫鼠游戏充满了挑战和乐趣。理解mtgsig这样的加密算法不仅能解决具体的技术问题更能让你深刻体会到现代Web安全技术的精妙之处。希望这篇详细的解析和实现指南能为你打开一扇门。记住关键在于耐心、细致的分析和不断的实践验证。