欢迎光临
我们一直在努力

快手APP逆向实战:Python复现sig3与tokensig签名算法

1. 项目概述与核心价值

最近在移动安全研究圈里,快手APP的签名算法一直是大家讨论的热点。无论是做数据采集、风控研究,还是单纯想理解大型APP如何保护其API通信安全,逆向分析其签名机制都是一个绝佳的实战案例。这个项目标题“快手APP逆向实战:手把手教你用Python复现sig3和tokensig签名算法”直指核心,它不是一个泛泛而谈的教程,而是瞄准了快手APP中两个关键的、用于接口请求认证的签名参数:

sig3

tokensig

。对于刚接触逆向的新手,可能会觉得无从下手;对于有经验的开发者,也可能在复杂的混淆和加密逻辑中迷失方向。这篇文章的目的,就是充当你的“领航员”,用Python作为主要工具,带你一步步拆解、分析并最终复现这两个签名算法,让你不仅能拿到可运行的代码,更能透彻理解其背后的设计思路和实现逻辑。

简单来说,

sig3

tokensig

是快手APP与服务器端进行API交互时的“身份证”和“通行证”。服务器通过验证这两个签名的有效性,来判断请求是否来自合法的、未被篡改的客户端。我们的目标,就是通过逆向工程,弄清楚这个“身份证”和“通行证”是如何制作出来的,并用Python代码模拟这个过程。这个过程会涉及到安卓APK的反编译、Java代码的静态分析、关键算法的定位、以及最终用Python进行算法还原和模拟请求。无论你是想学习APP逆向的实战技巧,还是需要解决特定的数据采集需求,这篇文章提供的思路和代码都将是一份宝贵的参考资料。我会尽量用通俗的语言解释每个步骤,并提供完整的、可运行的代码片段,确保你能够跟着做下来。

2. 逆向工程的核心思路与准备工作

逆向工程不是盲目地乱翻代码,它需要一套清晰的策略。对于快手APP的签名算法逆向,我们的核心思路可以概括为“由外及内,动态结合静态”。首先,我们需要理解签名的应用场景:它出现在网络请求中。因此,我们的切入点就是捕获和分析快手APP发出的网络请求,特别是关注请求头(Headers)或请求体(Body)中那些看起来像随机字符串的参数,

sig3

tokensig

通常就在其中。

2.1 工具链准备:你的数字手术刀

工欲善其事,必先利其器。在开始之前,你需要准备好以下工具,它们构成了我们逆向分析的“手术台”:

  • 抓包工具

    :这是我们的“眼睛”。推荐使用

    Charles

    Fiddler

    。你需要配置好手机代理,并安装抓包工具的CA证书到手机中,以便能够解密HTTPS流量(对于快手这类APP,几乎全部是HTTPS)。抓包的目的很明确:找到包含

    sig3

    tokensig

    的请求,记录下完整的URL、Headers、Body。一个典型的发现可能是一个视频列表请求,其URL参数或Headers里带着长长的

    sig3

    字符串。

  • 反编译与静态分析工具

    :这是我们的“显微镜”。我们需要拆解APK文件,查看其Java/Smali代码。

    • APK提取

      :可以从手机中提取已安装的APP,或者从可靠的第三方市场下载特定版本的APK。

      注意

      :为了分析稳定性和代码可读性,建议选择一个较旧但功能完整的版本,因为最新版本的混淆和加固可能更强。

    • 反编译工具

      JADX-GUI

      是首选。它可以将APK中的DEX文件反编译成可读性非常高的Java代码,并且支持全局搜索、跳转引用,极大提升了分析效率。

    • 备用工具

      Apktool

      可以用来解包APK资源文件和

      Smali

      代码(一种安卓字节码的汇编语言)。当

      JADX

      反编译出的Java代码逻辑混乱时,查看

      Smali

      代码有时能获得更准确的控制流信息。

  • 动态调试工具(可选但推荐)

    :这是我们的“内窥镜”。静态分析有时会陷入复杂的调用链。使用动态调试工具,如

    Frida

    ,可以在APP运行时,注入我们的脚本,实时打印函数参数、返回值、甚至修改逻辑。这对于验证算法猜测、追踪加密函数的输入输出至关重要。

  • 开发环境

    :这是我们的“复原工作台”。我们需要

    Python 3.7+

    环境,以及一些关键的库:

    • requests

      : 用于模拟最终的网络请求。

    • frida-tools

      : 如果你使用Frida进行动态分析。

    • 可能用到的加密库:

      hashlib

      ,

      hmac

      ,

      Crypto

      (来自

      pycryptodome

      ),用于复现各种哈希和加密算法。

  • 2.2 逆向策略:从流量到代码

    准备好工具后,我们开始实施逆向策略:

  • 流量捕获与参数定位

    :启动抓包工具和快手APP,进行一些常规操作,如刷新首页、观看视频、查看评论。在抓包工具中过滤

    kuaishou.com

    或相关域名,仔细寻找请求中包含

    sig3

    tokensig

    的接口。记录下这个请求的所有细节,包括时间戳、其他参数(如

    __NS_sig3

    __NStokensig

    等可能的不同名称)。这是我们的“已知结果”,后续所有分析都为了复现生成这个结果的过程。

  • 搜索与定位

    :将APK文件拖入

    JADX-GUI

    。反编译完成后,利用其强大的搜索功能,直接搜索字符串

    sig3

    tokensig

    。你可能会找到许多包含这些字符串的类,比如参数拼接的代码、网络拦截器的代码、或者签名计算类的定义。重点关注那些看起来像是在“计算”、“生成”、“签名”的类和方法。通常,签名生成的代码会集中在一个或多个专门的

    XXXSigner

    SignatureHelper

    SecurityUtils

    这样的类中。

  • 关键代码分析

    :找到疑似签名生成的函数后(例如一个名为

    calculateSig3

    getTokenSig

    的方法),仔细阅读其代码。你需要关注:

    • 输入参数

      :这个方法接收哪些参数?通常是URL路径、查询参数、请求体、时间戳、设备信息等。

    • 算法流程

      :代码中调用了哪些加密相关函数?如

      MessageDigest.getInstance(“SHA-256”)

      Mac.getInstance(“HmacSHA256”)

      Cipher.getInstance(“AES/…”)

      等。这些是算法类型的直接提示。

    • 密钥来源

      :加密或HMAC所需的密钥从哪里来?可能是硬编码在代码中的字符串(经过简单编码),也可能是从服务器动态获取后保存在本地。寻找密钥的赋值和加载过程。

    • 输出处理

      :计算出的字节数组是如何变成最终的字符串的?常见的是Base64编码或十六进制(Hex)字符串。

  • 注意

    :商业APP的代码通常经过了混淆(Proguard)。类名、方法名、变量名可能变成了

    a

    ,

    b

    ,

    c

    这种无意义的字符。这时,你需要更多地依赖对代码逻辑(如循环、条件判断、API调用)的分析,以及结合动态调试来确认函数功能。

    3. sig3签名算法深度解析与复现

    sig3

    签名在快手的接口中扮演着请求完整性和身份验证的角色。根据对多个版本快手的逆向分析,其核心思想是对请求的特定要素(如URL路径、排序后的查询参数、请求体等)进行拼接,然后使用一种哈希算法(可能是变种的HMAC或自定义哈希)进行计算,最后进行编码输出。请注意,不同版本、不同接口的

    sig3

    算法细节可能有细微差别,以下是一个典型的、经过简化的逻辑还原,旨在阐明其核心原理和复现方法。

    3.1 算法输入与参数收集

    sig3

    的生成通常依赖于一组动态和静态的参数。我们的Python复现代码首先需要能模拟收集这些参数:

  • 请求路径(Path)

    :例如

    /api/v1/feed

  • 查询参数(Query Params)

    :将URL中的所有查询参数(如

    ?type=hot&page=1

    )进行收集。

    关键步骤

    :需要按照参数名的字典序(ASCII码顺序)进行排序。这是防止参数顺序不同导致签名不一致的常见做法。

  • 请求体(Body)

    :对于POST请求,需要获取其请求体内容。如果是JSON格式,通常直接使用原始JSON字符串(或去除空白字符后的字符串)。

  • 时间戳(Timestamp)

    :一个当前时间的Unix时间戳(秒级或毫秒级)。

  • 设备标识符

    :如

    device_id

    install_id

    openudid

    等,这些值通常从APP的本地存储或系统API中获取。在模拟时,我们可以从一个有效的抓包请求中提取并固定使用。

  • 盐值(Salt)或密钥

    :一个固定的、硬编码在APP中的字符串,用于增加哈希的复杂度,防止被轻易破解。这是算法的核心秘密之一,需要通过逆向分析在代码中寻找。

  • # 示例:参数收集与预处理
    import time
    import urllib.parse

    def collect_sig3_params(url, body_json=None, device_info=None):
    """
    收集并预处理用于计算sig3的参数。
    :param url: 完整的请求URL,如 https://api.kuaishou.com/api/v1/feed?type=hot&page=1
    :param body_json: POST请求的JSON体,字符串格式。
    :param device_info: 字典,包含 device_id, install_id 等信息。
    :return: 处理后的参数字典和排序后的查询字符串。
    """
    parsed_url = urllib.parse.urlparse(url)
    path = parsed_url.path # 例如:/api/v1/feed

    # 解析并排序查询参数
    query_params = urllib.parse.parse_qs(parsed_url.query)
    # 注意:parse_qs返回的值是列表,需要处理。这里简化,假设每个键对应单个值。
    sorted_query_items = sorted([(k, v[0] if isinstance(v, list) and len(v)==1 else v) for k, v in query_params.items()])
    sorted_query_str = '&'.join([f'{k}={v}' for k, v in sorted_query_items])

    # 请求体处理
    body_str = ''
    if body_json:
    # 实际中可能需要去除JSON中的空白字符,确保与APP行为一致
    import json
    body_str = json.dumps(json.loads(body_json), separators=(',', ':')) # 紧凑格式

    timestamp = int(time.time()) # 秒级时间戳

    params = {
    'path': path,
    'sorted_query': sorted_query_str,
    'body': body_str,
    'timestamp': timestamp,
    'device_id': device_info.get('device_id', ''),
    'install_id': device_info.get('install_id', ''),
    # … 其他设备参数
    }
    return params, sorted_query_str

    3.2 核心计算逻辑还原

    参数准备好后,接下来就是核心的签名计算。逆向分析发现,

    sig3

    的计算并非简单的标准HMAC,它可能包含多轮哈希、特定拼接规则和自定义的变换。

  • 拼接规则

    :将上述参数按照一个固定的顺序和分隔符拼接成一个大的字符串。例如:

    path + “|” + sorted_query_str + “|” + body_str + “|” + str(timestamp) + “|” + device_id + …

    。分隔符可能是竖线

    |

    、冒号

    :

    或空字符串,这需要逆向确认。

  • 哈希算法

    :对拼接后的字符串进行哈希运算。常见的选择是

    SHA-256

    。但快手可能使用了

    HMAC-SHA256

    ,即需要一个密钥(上文提到的盐值)。

    关键点

    :这个密钥可能不是直接使用,而是经过了某种预处理(如与另一个字符串拼接后再取哈希作为实际密钥)。

  • 编码输出

    :计算得到的哈希值(字节数组)会进行Base64编码。有时,Base64结果还会进行一些字符替换(如将

    +

    换成

    /

    换成

    _

    ,以符合URL安全要求),并可能截取特定长度。

  • # 示例:sig3计算核心函数(假设为HMAC-SHA256变种)
    import hmac
    import hashlib
    import base64

    def calculate_sig3_core(param_string, secret_salt):
    """
    计算sig3的核心哈希函数。
    :param param_string: 拼接好的参数字符串。
    :param secret_salt: 逆向得到的盐值或密钥。
    :return: sig3签名字符串。
    """
    # 步骤1:密钥预处理(示例:将盐值与固定字符串拼接后取SHA256作为HMAC密钥)
    key_seed = secret_salt + "a_fixed_string_from_app"
    hmac_key = hashlib.sha256(key_seed.encode('utf-8')).digest()

    # 步骤2:使用预处理后的密钥进行HMAC-SHA256计算
    signature = hmac.new(hmac_key, param_string.encode('utf-8'), hashlib.sha256).digest()

    # 步骤3:Base64编码并做URL安全处理
    sig_b64 = base64.urlsafe_b64encode(signature).decode('utf-8').rstrip('=')
    # 可能还有额外的字符替换,例如:
    # sig_b64 = sig_b64.replace('+', '-').replace('/', '_')

    # 步骤4:可能截取前N位(例如前16位)作为最终的sig3
    final_sig3 = sig_b64[:16]
    return final_sig3

    # 整合函数
    def generate_sig3(url, body_json, device_info, secret_salt):
    params, sorted_query = collect_sig3_params(url, body_json, device_info)
    # 按照逆向分析的顺序拼接字符串
    param_string_to_sign = f"{params['path']}|{params['sorted_query']}|{params['body']}|{params['timestamp']}|{params['device_id']}"
    sig3 = calculate_sig3_core(param_string_to_sign, secret_salt)
    return sig3, params['timestamp']

    实操心得

    secret_salt

    的寻找是逆向的难点。它可能被隐藏在字符串常量池里,可能被分割成多段,也可能经过了简单的XOR或Base64编码。在JADX中,可以搜索一些常见的加密算法初始化代码附近出现的字符串常量。动态调试

    Frida

    在这里可以大显身手:在疑似计算签名的函数入口处打印传入的密钥参数,是最直接有效的方法。

    4. tokensig签名算法深度解析与复现

    tokensig

    (或类似名称)通常与会话(Session)或令牌(Token)的生命周期绑定,用于验证用户身份的持续有效性。它可能依赖于一个长期有效的

    token

    (例如登录后下发的

    access_token

    ),并结合其他动态因子生成。其安全等级通常比

    sig3

    更高,算法也可能更复杂,可能涉及非对称加密或更复杂的密钥派生。

    4.1 tokensig的生成依赖

  • Access Token

    :用户登录后获得的核心凭证,是生成

    tokensig

    的基础。

  • 动态因子

    :如当前时间戳、请求的某种特征(可能是URL的哈希值)、一个随机数(Nonce)等,用于防止重放攻击。

  • 设备指纹

    :与

    sig3

    类似,会包含设备唯一标识。

  • 私钥或固定密钥

    :可能是存储在APP内的一个固定RSA私钥(PEM格式或其衍生值),或者一个用于HMAC的密钥。这个密钥的安全性非常高,可能被深度混淆或放在so库(Native代码)中。

  • 4.2 算法流程推测与复现

    由于

    tokensig

    涉及更高的安全性,其算法变种更多。这里提供两种最常见的思路:

    思路A:基于HMAC的增强令牌签名

    这种方式与

    sig3

    类似,但输入和密钥不同。

    def generate_tokensig_hmac(access_token, timestamp, device_fingerprint, secret_key):
    """
    假设tokensig是HMAC-SHA512的变种。
    """
    # 拼接规则需要逆向确定,例如:token + timestamp + device_fp
    message = f"{access_token}:{timestamp}:{device_fingerprint}"
    # 使用一个独立的、更强的密钥
    signature = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha512).digest()
    # 输出可能为Hex字符串,而非Base64
    tokensig = signature.hex()[:32] # 取前32位十六进制字符
    return tokensig

    思路B:基于非对称加密的签名

    这种更为安全,APP端使用私钥对某个消息进行签名。

    from Crypto.PublicKey import RSA
    from Crypto.Signature import pkcs1_15
    from Crypto.Hash import SHA256

    def generate_tokensig_rsa(access_token, timestamp, private_key_pem):
    """
    假设tokensig是RSA-PKCS1_v1_5签名。
    :param private_key_pem: 从APP中逆向提取出的PEM格式私钥字符串(可能被混淆)。
    """
    # 1. 拼接签名字符串
    message = f"{access_token}|{timestamp}"
    # 2. 计算消息的哈希
    digest = SHA256.new(message.encode('utf-8'))
    # 3. 加载私钥并签名
    private_key = RSA.import_key(private_key_pem)
    signer = pkcs1_15.new(private_key)
    signature_bytes = signer.sign(digest)
    # 4. 编码输出,可能是Base64
    import base64
    tokensig = base64.urlsafe_b64encode(signature_bytes).decode('utf-8').rstrip('=')
    return tokensig

    注意事项

    :在实际的逆向中,私钥几乎不可能以明文PEM格式存在。它很可能被分割、编码(如Base64)、或与其它数据XOR后存储。还原过程需要仔细分析密钥的加载和解码函数。此外,签名算法也可能是

    ECDSA

    等。动态调试

    Frida

    在这里几乎是必需品,可以挂钩到密码学库(如

    OpenSSL

    BouncyCastle

    )的相关函数,直接捕获输入、输出和密钥。

    4.3 整合与请求模拟

    当我们成功复现了

    sig3

    tokensig

    的生成函数后,最后一步就是将它们整合到网络请求中,模拟一个完整的、可以被服务器接受的请求。

    import requests
    import json

    def make_kuaishou_request(api_url, method='GET', post_data=None, access_token=None, device_info=None, secrets=None):
    """
    模拟发送一个快手API请求。
    :param secrets: 字典,包含 sig3_salt, tokensig_key 等逆向得到的密钥。
    """
    # 1. 生成sig3
    body_json_str = json.dumps(post_data) if post_data else None
    sig3, timestamp = generate_sig3(api_url, body_json_str, device_info, secrets.get('sig3_salt'))

    # 2. 生成tokensig (如果有token)
    tokensig = None
    if access_token:
    # 假设使用HMAC方式
    device_fp = device_info.get('device_id', '') + device_info.get('install_id', '')
    tokensig = generate_tokensig_hmac(access_token, timestamp, device_fp, secrets.get('tokensig_key'))

    # 3. 构造请求头
    headers = {
    'User-Agent': 'Kwai-Android/…', # 模拟真实的快手UA
    'Content-Type': 'application/json',
    'X-REQUEST-ID': str(timestamp), # 或其他生成ID的方式
    }
    if sig3:
    headers['X-SIG3'] = sig3 # 实际Header名需根据抓包确定
    if tokensig:
    headers['X-TOKENSIG'] = tokensig # 实际Header名需根据抓包确定
    if access_token:
    headers['Authorization'] = f'Bearer {access_token}'

    # 4. 发送请求
    if method.upper() == 'GET':
    response = requests.get(api_url, headers=headers)
    else:
    response = requests.post(api_url, headers=headers, json=post_data)

    return response

    # 使用示例
    device_info = {'device_id': 'your_device_id', 'install_id': 'your_install_id'}
    secrets = {'sig3_salt': '逆向得到的盐值', 'tokensig_key': '逆向得到的密钥'}
    access_token = 'your_access_token_if_logged_in'

    api_url = 'https://api.kuaishou.com/api/v1/feed?type=hot'
    resp = make_kuaishou_request(api_url, device_info=device_info, secrets=secrets, access_token=access_token)
    print(resp.status_code, resp.json())

    5. 逆向过程中的常见问题与排查技巧

    即使有了清晰的思路和工具,逆向过程也绝不会一帆风顺。下面记录了一些典型问题和我的解决经验。

    5.1 代码混淆严重,找不到关键函数

    • 问题

      :在JADX中搜索

      sig3

      ,可能只找到一些字符串常量,而计算逻辑被隐藏在以

      a.a.a.a

      命名的类中。

    • 排查技巧

    • 关注调用栈

      :在抓包工具中,你可以看到请求发出。尝试在JADX中全局搜索你抓包到的完整URL路径的一部分,或者搜索明显的网络库类(如

      OkHttp

      Interceptor

      接口实现类)。网络拦截器是签名注入的常见位置。

    • 动态跟踪

      :使用

      Frida

      Hook所有

      java.security.MessageDigest

      javax.crypto.Mac

      类的

      getInstance

      update

      /

      doFinal

      方法。当APP运行时,这些Hook点会打印出堆栈信息,告诉你是在哪个类的方法里调用了加密函数,从而逆向定位到入口。

    • 特征字符串搜索

      :搜索算法名称的字符串片段,如

      “SHA”

      “HMAC”

      “AES”

      “/ECB/”

      等,这些地方附近往往是加密相关代码。

    5.2 算法逻辑复杂,拼接顺序难以确定

    • 问题

      :知道了用HMAC-SHA256,但不知道哪些参数、以什么顺序和分隔符拼接。

    • 排查技巧

    • 对比实验法

      :用

      Frida

      Hook疑似签名函数,在多个不同请求(不同URL、不同参数)下,打印出函数的

      输入字符串

      输出签名

      。通过对比多组数据,可以反推拼接规则。例如,如果两个请求只有时间戳不同,那么输入字符串中变化的部分很可能就包含了时间戳。

    • 参数篡改测试

      :在Hook中,尝试修改传入函数的某个参数(如将时间戳加1),观察输出签名的变化。如果签名完全变了,说明该参数参与了计算;如果没变,则可能没参与。

      注意

      :篡改可能引发APP崩溃,需谨慎。

    • 日志分析

      :有些APP在调试版本或特定条件下会输出详细的日志。可以尝试在

      Logcat

      中过滤APP的日志标签,寻找签名计算相关的打印信息。

    5.3 密钥被隐藏或动态生成

    • 问题

      :找到了算法,但用于HMAC的密钥或用于RSA签名的私钥不是简单的字符串,而是一段看起来像乱码的数据,或者是从另一个函数调用返回的。

    • 排查技巧

    • 追溯来源

      :在JADX中,查看该密钥变量的赋值语句,向上追溯它的来源。它可能来自一个

      getKey()

      方法,该方法可能从本地文件、数据库或一个复杂的初始化函数中读取。

    • 内存DUMP

      :对于深度混淆或Native层的密钥,可以在APP运行起来、密钥已加载到内存后,使用

      Frida

      脚本搜索内存中的特定模式(如PEM密钥的头尾

      —–BEGIN PRIVATE KEY—–

      ),或者直接Dump持有密钥对象的内存区域。

    • 静态分析Native库(.so文件)

      :如果密钥逻辑在C/C++层,需要使用

      IDA Pro

      Ghidra

      反汇编so文件进行分析。这难度较大,通常需要先确认Java层调用了哪些Native方法。

    5.4 复现的签名与服务端校验不匹配

    • 问题

      :所有代码都写好了,但生成的签名服务器不认可,返回签名错误。

    • 排查技巧

    • 严格比对输入

      :确保你的Python代码收集的参数与APP运行时完全一致。包括:URL编码(空格是

      %20

      还是

      +

      ?)、JSON字符串格式(是否有多余空格、换行符?字典顺序是否一致?)、时间戳精度(秒还是毫秒?)。

    • 检查编码细节

      :哈希前的字符串编码是

      UTF-8

      还是

      GBK

      ?Base64编码是标准还是URL安全的?是否有额外的字符替换或截取?

    • 分步验证

      :如果可能,用

      Frida

      在APP运行时,将你的Python代码生成的中间结果(拼接后的字符串)与APP内部的中间结果进行比对。也可以将APP计算出的最终签名与你生成的签名进行比对,看差异在哪里。

    • 版本差异

      :确认你分析的APK版本与你抓包使用的APP版本是否一致。不同版本的算法可能有升级。

    下表总结了一些常见错误和排查方向:

    问题现象

    可能原因

    排查方向

    签名完全不对 算法根本错误 重新确认Hook到的加密函数类型(SHA256? HMAC?)。检查密钥是否正确。
    签名部分匹配(前几位相同) 编码或截取问题 检查Base64/Hex编码方式,检查签名是否被截断(取前N位)。
    只有带特定参数的请求失败 参数拼接顺序或遗漏 对比成功和失败请求的Hook输入字符串,找出差异点。检查是否遗漏了某些固定参数(如版本号)。
    时间戳相关错误 时间戳格式或同步问题 检查时间戳是10位(秒)还是13位(毫秒)。检查是否需要与服务器时间同步。
    仅在登录后请求失败 tokensig算法错误或token状态 确认

    access_token

    是否正确。检查

    tokensig

    算法是否依赖了其他动态因子(如请求ID)。

    逆向工程是一场与开发者的智力博弈,需要耐心、细心和大量的尝试。每一次成功的复现,不仅解决了一个具体问题,更是对移动应用安全机制的一次深刻理解。希望这份详细的指南和代码框架,能为你打开快手APP逆向,乃至更广阔的移动安全研究之门。记住,核心思路和调试方法比某一行代码更重要。

    赞(0)
    未经允许不得转载:171主机测评 » 快手APP逆向实战:Python复现sig3与tokensig签名算法
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址