保管时间 026/02/10 保管单位 Redstix 等级 main
学海签名方式解析
前言
在 ZTY/ZTP⁽¹⁾ 中,学海通过对 okhttp 的定制实现了对几乎所有 ztp.yunzuoye.net REST API⁽²⁾ 的请求的签名,从而加大了 API 研究的难度,
比如直接访问 ztp.yunzuoye.net/api/v2/platform/apk 就会显示 Missing sign parameter!!!
本文记录了学海的这种签名方式。
源码原理
注意到,在智通云/智通平台的 OkHttpClientManager 中,学海添加了一个 RequestSignInterceptor ⁽³⁾,它负责拦截请求,签名,再把它发出去。
而 RequestSignInterceptor 调用了 RequestSignUtil 中的 addRestfulVerifyData 进行了具体的签名,它长这样
public static String addRestfulVerifyDataV2(String str, String str2, String str3, String str4, String str5, String str6, SignListener signListener) { long jCurrentTimeMillis = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); sb.append(str); sb.append(str2); sb.append(jCurrentTimeMillis); sb.append(str4); if (!needCombineBodyDataIntoUrl(str) && !isEmpty(str5) && str5.getBytes().length < 1048576) { sb.append(str5); } if (str6 != null) { sb.append("Authorization: " + str6); } String string = sb.toString(); if (signListener != null) { signListener.onSignatureBodyReady(string); } String strEncode = MD5Util.encode(string); StringBuilder sb2 = new StringBuilder(str2); if (str2.contains("?")) { sb2.append(ContainerUtils.FIELD_DELIMITER); } else { sb2.append("?"); } sb2.append("sign"); sb2.append(ContainerUtils.KEY_VALUE_DELIMITER); sb2.append(strEncode); sb2.append(str3); sb2.append(ContainerUtils.FIELD_DELIMITER); sb2.append("t"); sb2.append(ContainerUtils.KEY_VALUE_DELIMITER); sb2.append(jCurrentTimeMillis); return sb2.toString();}这就是学海的具体签名函数了⁽⁴⁾。
分析
为了清晰的表述其工作方式,注释后的代码如下,删去了许多内容
/** * 注释版 * * @param method 请求方式 * @param url 原始URL * @param signatureKey SignatureKey * @param requestBody 请求体 * @param authHeader 请求头中的Authorization * @return String 签好的新 URL */public static String addRestfulVerifyData(String method,String url,String,String requestBody,String authHeader) {
long timestamp = System.currentTimeMillis();
String rawSignString = method + url + timestamp + signatureKey;
// 不是GET/DELETE、Body非空且Body小于1MB时 if (!needCombineBodyDataIntoUrl(method) && !isEmpty(requestBody) && requestBody.getBytes().length < 1048576) { rawSignString+=requestBody; }
// 如果有认证头,也参与签名 if (authHeader != null) { rawSignString+="Authorization: "+authHeader; } String sign = MD5Util.encode(rawSignString);
StringBuilder finalUrlBuilder = new StringBuilder(url);
// 处理URL连接符 if (url.contains("?")) { finalUrlBuilder.append("&"); } else { finalUrlBuilder.append("?"); }
finalUrlBuilder.append("sign=").append(sign); finalUrlBuilder.append("&t=").append(timestamp);
return finalUrlBuilder.toString();}请求方式是待签名请求方法的大写名字,如 GET、PATCH。
其中,signatureKey通过JNI层访问xhcore.so中 Java_com_xh_xhcore_jni_XHCoreJni_getSignatureKey 的获取,
它就是 eptim]q34imt5b]-q04i5q=fdkfjfsadlkjfasdfrt573df4pltoy]-pn965498d⁽⁵⁾,我们有理由怀疑这是学海程序猿手在键盘上滚出来的
一个签名示例
[*] 待签名串: GEThttps://ztp.yunzuoye.net/api/v2/platform/apk?appId=mdm_stu_to_c&versionCode=10210061770653399094eptim]q34imt5b]-q04i5q=fdkfjfsadlkjfasdfrt573df4pltoy]-pn965498d[*] 最终 URL: https://ztp.yunzuoye.net/api/v2/platform/apk?appId=mdm_stu_to_c&versionCode=1021006&sign=314aafb4083bcfef7ed0ffba1bd0334f&t=1770653399094Python实现
def get_zty_sign_url_3(method, url, request_body=None, auth_header=None): signature_key="eptim]q34imt5b]-q04i5q=fdkfjfsadlkjfasdfrt573df4pltoy]-pn965498d" timestamp = str(int(time.time() * 1000)) raw_sign_string = f"{method}{url}{timestamp}{signature_key}" if method not in ["GET", "DELETE"] and request_body and len(request_body.encode('utf-8')) < 1048576: raw_sign_string += request_body if auth_header: raw_sign_string += f"Authorization: {auth_header}" sign = hashlib.md5(raw_sign_string.encode('utf-8')).hexdigest() if "?" in url: final_url = f"{url}&sign={sign}&t={timestamp}" else: final_url = f"{url}?sign={sign}&t={timestamp}" return final_url注1: 由于目前未分析学海的其它软件,暂时无法确定该签名的覆盖软件范围
注2: 同理,暂时无法确定该签名的覆盖API范围,不过若代码里是用LRequest发起请求的,则一定是签名了
注3: 具体位置 com.xh.xhcore.common.http.strategy.okhttp.interceptors.RequestSignUtil
注4:在代码中,还有 RequestSignInterceptorV2,但并未被使用,猜测为学海史山代码遗留
注5:SignatureKey 最终在 IDA 虚拟地址 0000708C 中可找到
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





