ARTICLE · 1066036
某办公软件Authorization 算法逆向分析还原
阅读须知
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号联系作者立即删除
1. 分析目标
目标请求示例:
GET https://xxx.com/api/v3/dev/oauth/wechat/accesstoken?appid=&code=HTTP/1.1Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:87cf234b497e595316c29cc759a86ad887f7f28cContent-MD5: d74b6ae81883eb7705c5eddd1697567fContent-Type: application/json; charset=utf-8Date: Tue, 14 Jul 2026 03:02:56 GMT目标是还原 Authorization 头的生成逻辑。
说明:本报告基于当前 APK/JADX 与
libcp-lib.so/IDA 分析结果
2. 结论摘要
Authorization 格式:
Authorization = "WPS-2:" + AK + ":" + Signature其中:
AK = AqY7ik9XQ92tvO7+NlCRvA==SK = 6a80e70986ade7855786e399f14bce4620129ba1签名公式:
Signature = SHA1_HEX(SK + Content-MD5 + Content-Type + Date)字段顺序严格为:
SKContent-MD5Content-TypeDate最终:
Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:SHA1_HEX( "6a80e70986ade7855786e399f14bce4620129ba1" + Content-MD5 + "application/json; charset=utf-8" + Date)3. AK/SK 来源
3.1 AK 来源
AK 不是 native 解密得到的,而是 Java 层硬编码:
AqY7ik9XQ92tvO7+NlCRvA==相关位置包括:
cn.wpsx.support.base.net.okhttp3.helper.BaseRequestHelper cn.wps.yunkit.YunConfig cn.wps.yunkit.api.qing.QingBaseApi cn.wps.yunkit.api.company.CompanyBaseApi
BaseRequestHelper 构造函数:
public BaseRequestHelper() { String sk = InternalGlobal.m376729c().mo170015c(); this.f436857b = sk; this.f436858c = new SignKeyPair( "AqY7ik9XQ92tvO7+NlCRvA==", sk );}所以:
AK = "AqY7ik9XQ92tvO7+NlCRvA=="Base64 解码后为 16 字节:
02a63b8a4f5743ddadbceefe365091bc它更像服务端分配的 access id / key id,而不是需要解密的密文。
3.2 SK 来源
SK 由 native 层 CpUtil.getPS() 解密得到。
调用链:
BaseRequestHelper() -> InternalGlobal.m376729c().mo170015c() -> NetLibFuncConfig.mo170015c() -> CpUtil.getPS("helper_baserequesthelper_cp") -> libcp-lib.so / Java_cn_wps_cp_util_CpUtil_getPS -> RSA public decrypt(blob_0x275EB) -> 6a80e70986ade7855786e399f14bce4620129ba1NetLibFuncConfig.mo170015c():
@Overridepublic String mo170015c() { return CpUtil.getPS("helper_baserequesthelper_cp");}IDA 中定位:
"helper_baserequesthelper_cp" string address: 0x2547cxref in Java_cn_wps_cp_util_CpUtil_getPS: 0x5b108cipher blob: 0x275EB解密结果:
helper_baserequesthelper_cp = 6a80e70986ade7855786e399f14bce4620129ba14. Authorization 生成代码路径
关键类:
cn.wpsx.support.base.net.okhttp3.helper.SignKeyPair关键方法:
public void m377103j(HttpRequest httpRequest, InputStream inputStream, String contentType)核心逻辑:
public void m377103j(HttpRequest httpRequest, InputStream inputStream, String contentType) { String contentMd5 = m377098c(inputStream, httpRequest.getUrl()); String date = m377095d(new Date()); this.f436874c = m377101h(contentType, contentMd5, date); String authorization = String.format( Locale.US, "WPS-%d:%s:%s", 2, this.f436872a, this.f436874c ); builder.addHeader("Content-Type", contentType); builder.addHeader("Content-MD5", contentMd5); builder.addHeader("Date", date); builder.addHeader("Authorization", authorization);}5. Signature 计算
关键方法:
public final String m377101h(String contentType, String contentMd5, String date) { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(this.f436873b.getBytes("utf-8")); // SK md.update(contentMd5.getBytes("utf-8")); md.update(contentType.getBytes("utf-8")); md.update(date.getBytes("utf-8")); return hex(md.digest());}还原为伪代码:
signature = sha1_hex( SK + content_md5 + content_type + date)authorization = f"WPS-2:{AK}:{signature}"6. Content-MD5 计算规则
关键方法:
public final String m377098c(InputStream inputStream, String url) { if (inputStream != null) { byte[] body = readAll(inputStream); if (body != null && body.length > 0) { return md5(body); } } return md5(url.getBytes("utf-8"));}6.1 GET 请求
GET 请求通常无 body,因此:
Content-MD5 = MD5(path + "?" + query)示例:
MD5("/api/v3/dev/oauth/wechat/accesstoken?appid=wx3ebc9e606b4f9242&code=083gL9Ga1A7s4M0fD2Ga1hEPtK2gL9Gh")= d74b6ae81883eb7705c5eddd1697567f6.2 POST 请求
POST 请求有 body 时:
Content-MD5 = MD5(raw_body_bytes)注意必须使用原始请求 body 字节,不能重新格式化 JSON。
7. Date 格式
关键方法:
public static String m377095d(Date date) { SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("GMT+00:00")); return sdf.format(date).concat("GMT");}格式:
Tue, 14 Jul 2026 03:02:56 GMT签名时必须使用请求实际携带的 Date 头,否则第三段签名不同。
8. 抓包验证
8.1 GET 示例
请求:
GET https://xxx.com/api/v3/dev/oauth/wechat/accesstoken?appid=&code=Content-MD5: d74b6ae81883eb7705c5eddd1697567fContent-Type: application/json; charset=utf-8Date: Tue, 14 Jul 2026 03:02:56 GMT签名源:
6a80e70986ade7855786e399f14bce4620129ba1d74b6ae81883eb7705c5eddd1697567fapplication/json; charset=utf-8Tue, 14 Jul 2026 03:02:56 GMT计算结果:
SHA1(...) = 87cf234b497e595316c29cc759a86ad887f7f28c最终:
Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:87cf234b497e595316c29cc759a86ad887f7f28c与抓包一致。
8.2 POST 示例
请求:
POST https://xxx.com/api/v3/app/oauth/verifyContent-MD5: b08cac688dd521f1860e0f8534f659b1Content-Type: application/json; charset=utf-8Date: Tue, 14 Jul 2026 03:08:02 GMT签名源:
6a80e70986ade7855786e399f14bce4620129ba1b08cac688dd521f1860e0f8534f659b1application/json; charset=utf-8Tue, 14 Jul 2026 03:08:02 GMT计算结果:
SHA1(...) = 23e2d5a82456409a265d6145d2c7598fdccce8e8最终:
Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:23e2d5a82456409a265d6145d2c7598fdccce8e8与抓包一致。
9. 最终算法
AK = "AqY7ik9XQ92tvO7+NlCRvA=="SK = CpUtil.getPS("helper_baserequesthelper_cp") = "6a80e70986ade7855786e399f14bce4620129ba1"if request_body exists and len(body) > 0: content_md5 = MD5(raw_body_bytes)else: content_md5 = MD5(path_query.encode("utf-8"))content_type = "application/json; charset=utf-8"date = GMT HTTP date stringsignature = SHA1_HEX(SK + content_md5 + content_type + date)authorization = "WPS-2:" + AK + ":" + signature免责声明
本报告仅供安全研究和学习目的,请勿用于非法用途。本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号联系作者立即删除