最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】小花生app中调用接口parentChildReadingBookQuery2时timestamp和signature生成的逻辑

timestamp crifan 1250浏览 0评论
折腾:
【已解决】PySpider模拟小花生app请求parentChildReadingBookQuery2返回空数据
期间,已确定,获取parentChildReadingBookQuery2的不同分页数据时
offset不同:
0
10
20
对应的timestamp和signature值,都是不同的
所以接下来就是要去研究具体生成的机制了。
去看之前:
【已解决】从不同版本的小花生apk中反编译出包含业务逻辑代码的dex和jar包源码
破解到的java源码,看能否找到逻辑。
com/huili/readingclub/activity/classroom/SelfReadingActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  private void getSupportingResources()
  {
    JsonObject localJsonObject1 = new JsonObject();
    JsonObject localJsonObject2 = new JsonObject();
    localJsonObject2.addProperty("userId", MainActivity.userId);
    localJsonObject2.addProperty("fieldName", this.mFieldName);
    localJsonObject2.addProperty("fieldValue", this.mFieldValue);
    localJsonObject2.addProperty("grade", this.mGrades);
    localJsonObject2.addProperty("level", this.mLevels);
    localJsonObject1.addProperty("J", localJsonObject2.toString());
    localJsonObject1.addProperty("C", Integer.valueOf(0));
    XutilsHttpClient.sendHttpJson(this, HttpRequest.HttpMethod.POST, "http://www.xiaohuasheng.cn:83/Reading.svc/getSupportingResourcesInSelfReadingBookQueryCondition", localJsonObject1.toString(), new RequestCallBack()
    {
...
-》没有看到timestamp和signature
-》说明是类库封装的统一的生成的
-》去看:
XutilsHttpClient.sendHttpJson
->
com/huili/readingclub/network/XutilsHttpClient.java
1
2
public static void sendHttpJson(Context paramContext, HttpRequest.HttpMethod paramHttpMethod, String paramString1, String paramString2, RequestCallBack<String> paramRequestCallBack)
...
找找
最后通过搜:
“signature”
找到了:
com/huili/readingclub/model/ModelSecurity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  public static void addSignature(RequestParams paramRequestParams, String paramString)
  {
    Object localObject = paramString;
    if (paramString.startsWith("http://www.xiaohuasheng.cn:83")) {
      localObject = paramString.substring("http://www.xiaohuasheng.cn:83".length());
    }
    if (((String)localObject).startsWith("/UserService.svc/getToken/")) {
      return;
    }
    int i;
    if (StringUtil.isNullOrEmpty(MainActivity.userId)) {
      i = 0;
    } else {
      i = Integer.parseInt(MainActivity.userId);
    }
    int j = i;
    if (i < 1) {
      j = 0;
    }
    paramString = "";
    if (j != 0) {
      paramString = getToken(MainActivity.userId);
    }
    long l = DateUtils.get1970ToNowSeconds();
    if (j == 0)
    {
      paramString = new StringBuilder();
      paramString.append(l);
      paramString.append((String)localObject);
      paramString.append(“AyGt7ohMR!xx#N");
      paramString = StringUtil.md5(paramString.toString());
    }
    else
    {
      StringBuilder localStringBuilder = new StringBuilder();
      localStringBuilder.append(MainActivity.userId);
      localStringBuilder.append(l);
      localStringBuilder.append((String)localObject);
      localStringBuilder.append(paramString);
      localStringBuilder.append(“AyGt7ohMR!xx#N");
      paramString = StringUtil.md5(localStringBuilder.toString());
    }
    if (j != 0) {
      paramRequestParams.addHeader("userId", MainActivity.userId);
    }
    localObject = new StringBuilder();
    ((StringBuilder)localObject).append(l);
    ((StringBuilder)localObject).append("");
    paramRequestParams.addHeader("timestamp", ((StringBuilder)localObject).toString());
    paramRequestParams.addHeader("signature", paramString);
  }
后记:后来发现Jadx导出的源码可读性更好:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    public static void addSignature(RequestParams requestParams, String str) {
        if (str.startsWith(MyConfig.SERVER_PORT)) {
            str = str.substring(MyConfig.SERVER_PORT.length());
        }
        if (!str.startsWith("/UserService.svc/getToken/")) {
            StringBuilder stringBuilder;
            int parseInt = StringUtil.isNullOrEmpty(MainActivity.userId) ? 0 : Integer.parseInt(MainActivity.userId);
            if (parseInt < 1) {
                parseInt = 0;
            }
            String str2 = "";
            if (parseInt != 0) {
                str2 = getToken(MainActivity.userId);
            }
            long j = DateUtils.get1970ToNowSeconds();
            if (parseInt == 0) {
                stringBuilder = new StringBuilder();
                stringBuilder.append(j);
                stringBuilder.append(str);
                stringBuilder.append(MyConfig.SECRET_KEY);
                str = StringUtil.md5(stringBuilder.toString());
            } else {
                StringBuilder stringBuilder2 = new StringBuilder();
                stringBuilder2.append(MainActivity.userId);
                stringBuilder2.append(j);
                stringBuilder2.append(str);
                stringBuilder2.append(str2);
                stringBuilder2.append(MyConfig.SECRET_KEY);
                str = StringUtil.md5(stringBuilder2.toString());
            }
            if (parseInt != 0) {
                requestParams.addHeader(USER.USERID, MainActivity.userId);
            }
            stringBuilder = new StringBuilder();
            stringBuilder.append(j);
            stringBuilder.append("");
            requestParams.addHeader("timestamp", stringBuilder.toString());
            requestParams.addHeader("signature", str);
        }
    }
从而更加利于理解程序的逻辑。
另外又看到了addSignature被调用的地方:
com/huili/readingclub/network/XutilsHttpClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  public static void sendHttpJson(Context paramContext, HttpRequest.HttpMethod paramHttpMethod, String paramString1, String paramString2, RequestCallBack<String> paramRequestCallBack)
  {
    if (!isNetWorkAvaiable(paramContext))
    {
      paramRequestCallBack.onFailure(new HttpException("检查网络设置"), "检查网络设置");
      return;
    }
    RequestParams localRequestParams = new RequestParams();
    localRequestParams.addHeader("Content-Type", "application/json");
    localRequestParams.addHeader("Authorization", "NSTp9~)NwSfrXp@\\");
    try
    {
      if (paramHttpMethod == HttpRequest.HttpMethod.GET) {
        ModelSecurity.addSignature(localRequestParams, paramString1);
      } else if (paramHttpMethod == HttpRequest.HttpMethod.POST) {
        ModelSecurity.addSignature(localRequestParams, (String)JsonUtil.jsonToMap(paramString2).get("J"));
      }
    }
...
可见:
此处当POST时,传入了:
  • localRequestParams
  • J字段
然后想办法看看如何转换成python代码:
【已解决】Python实现小花生中addSignature的md5加密生成签名的逻辑
得到了正确的计算signature的逻辑后,再去
python生成timestamp,然后调用signature的md5值的计算逻辑,加上相关函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2019-03-27 15:35:20
# Project: XiaohuashengApp
 
 
from pyspider.libs.base_handler import *
 
 
import os
import json
import codecs
import base64
import gzip
import copy
import time
# import datetime
from datetime import datetime, timedelta
from hashlib import md5
 
 
######################################################################
# Const
######################################################################
 
 
 
 
RESPONSE_OK = "1001"
 
 
######################################################################
# Config & Settings
######################################################################
 
 
OutputFolder = "/Users/crifan/dev/dev_root/company/xxx/projects/crawler_projects/crawler_xiaohuasheng_app/output"
 
 
DefaultPageSize = 10
 
 
UserAgentNoxAndroid = "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; A0001 Build/KOT49H) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
 
 
gUserId = "1134723"
gAuthorization = """NSTp9~)NwSfrXp@\\"""
 
 
# Timestamp = "1553671960"
# Signature = "bc8d887e7f16ef92269f55bfd74e131b"
# Timestamp = "1553671956"
# Signature = "a1f51f6852f42d934f0ac154dc374500"
# Timestamp = "1553675572"
# Signature = "d516df23120d3c9f0397475f03c61199"
# gTimestamp = "1553845333"
# gSignature = "2c30a3ac5898fa43eeececfa21560f5b"
 
 
gUserToken = "40d2267f-359e-4526-951a-66519e5868c3"
gSecretKey = “AyGt7ohMR!xxx#N"
 
gHeaders = {
    "Host": "www.xiaohuasheng.cn:83",
    "User-Agent": UserAgentNoxAndroid,
    "Content-Type": "application/json",
 
 
    "userId": gUserId,
    "Authorization": gAuthorization,
    # "timestamp": gTimestamp,
    # "signature": gSignature,
 
 
    "cookie": "ASP.NET_SessionId=dxf3obxgn5t4w350xp3icgy0",
    # "Cookie2": "$Version=1",
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "cache-control": "no-cache",
 
 
    "Connection": "keep-alive",
    # "content-length": "202",
}
 
 
######################################################################
# Common Util Functions
######################################################################
 
 
def getCurTimestamp(withMilliseconds=False):
    """
    get current time's timestamp
        (default)not milliseconds -> 10 digits: 1351670162
        with milliseconds -> 13 digits: 1531464292921
    """
    curDatetime = datetime.now()
    return datetimeToTimestamp(curDatetime, withMilliseconds)
 
 
def datetimeToTimestamp(datetimeVal, withMilliseconds=False) :
    """
        convert datetime value to timestamp
        eg:
            "2006-06-01 00:00:00.123" -> 1149091200
            if with milliseconds -> 1149091200123
    :param datetimeVal:
    :return:
    """
    timetupleValue = datetimeVal.timetuple()
    timestampFloat = time.mktime(timetupleValue) # 1531468736.0 -> 10 digits
    timestamp10DigitInt = int(timestampFloat) # 1531468736
    timestampInt = timestamp10DigitInt
 
 
    if withMilliseconds:
        microsecondInt = datetimeVal.microsecond # 817762
        microsecondFloat = float(microsecondInt)/float(1000000) # 0.817762
        timestampFloat = timestampFloat + microsecondFloat # 1531468736.817762
        timestampFloat = timestampFloat * 1000 # 1531468736817.7621 -> 13 digits
        timestamp13DigitInt = int(timestampFloat) # 1531468736817
        timestampInt = timestamp13DigitInt
 
 
    return timestampInt
...
 
 
######################################################################
# Main
######################################################################
 
 
class Handler(BaseHandler):
    crawl_config = {
        # 'headers': {
        #     "Host": "www.xiaohuasheng.cn:83",
        #     "User-Agent": UserAgentNoxAndroid,
        #     "Content-Type": "application/json",
 
 
        #     "userId": gUserId,
        #     "Authorization": gAuthorization,
        #     # "timestamp": gTimestamp,
        #     # "signature": gSignature,
 
 
        #     "cookie": "ASP.NET_SessionId=dxf3obxgn5t4w350xp3icgy0",
        #     # "Cookie2": "$Version=1",
        #     "Accept": "*/*",
        #     "Accept-Encoding": "gzip, deflate",
        #     "cache-control": "no-cache",
 
 
        #     "Connection": "keep-alive",
        #     # "content-length": "202",
        # },
    }
 
 
    def on_start(self):
        offset = 0
        limit = DefaultPageSize
        self.getParentChildReading(offset, limit)
 
 
    def getParentChildReading(self, offset, limit):
        print("offset=%d, limit=%d" % (offset, limit))
 
 
        # jTemplate = """{"userId":"%s","fieldName":"","fieldValue":"全部类别","theStageOfTheChild":"","parentalEnglishLevel":"","supportingResources":"有音频","offset":%d,"limit":%d}"""
        jTemplate = "{\"userId\":\"%s\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":%d,\"limit\":%d}"
        jcJsonDict = {
            "J": jTemplate % (gUserId, offset, limit),
            "C": 0
        }
        # print("jcJsonDict=%s" % jcJsonDict)
 
 
        parentChildReadingParamDict = {
            "offset": offset,
            "limit": limit,
            "jTemplate": jTemplate,
            "jcJsonDict": jcJsonDict
        }
 
 
        # for debug
        # jcJsonDict = {"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"\",\"offset\":0,\"limit\":10}","C":0}
        # jcJsonDict = {"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":0,\"limit\":10}","C":0}
        # jcJsonDict = {"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"grades\":\"\",\"levels\":\"\",\"supportingResources\":\"有音频\",\"offset\":0,\"limit\":10}","C":0}
        # jcJsonDict = {"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"grades\":\"\",\"levels\":\"\",\"supportingResources\":\"\",\"offset\":0,\"limit\":10}","C":0}
        # jcJsonDict = {"J":'{"userId":"1134723","fieldName":"","fieldValue":"全部类别","grades":"","levels":"","supportingResources":"","offset":0,"limit":10}',"C":0}
        # jcJsonDict = {"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":0,\"limit\":10}","C":0}
 
 
        jValueStr = jcJsonDict["J"]
        # print("jValueStr=%s" % jValueStr)
 
 
        jcJsonDictStr = json.dumps(jcJsonDict)
        # print("jcJsonDictStr=%s" % jcJsonDictStr)
 
 
        # # for debug
        # jcJsonDictStr = '{"J":"{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"\",\"offset\":0,\"limit\":10}","C":0}'
        # print("jcJsonDictStr=%s" % jcJsonDictStr)
 
 
        curHeaders = copy.deepcopy(gHeaders)
        # print("curHeaders=%s" % curHeaders)
        # print("type(curHeaders)=%s" % type(curHeaders))
        curTimestampInt = getCurTimestamp()
 
 
        # # for debug
        # curTimestampInt = int(gTimestamp)
 
 
        # print("curTimestampInt=%s" % curTimestampInt)
        curTimestampStr = str(curTimestampInt)
 
 
        # print("curTimestampStr=%s" % curTimestampStr)
        curHeaders["timestamp"] = curTimestampStr
        # jValueParamStr = jcJsonDictStr
        # jcJsonDictFormattedStr = "%s" % jcJsonDict
        # jcJsonDictFormattedStr = jcJsonDictFormattedStr.replace(" ", "")
        # print("jcJsonDictFormattedStr=%s" % jcJsonDictFormattedStr)
        # jValueParamStr = jcJsonDictFormattedStr
        jValueParamStr = jValueStr
        calculatedSignature = self.generateSignature(curTimestampInt, jValueParamStr)
        # print("calculatedSignature=%s" % calculatedSignature)
        curHeaders["signature"] = calculatedSignature
 
 
        # # for debug
        # curHeaders["signature"] = gSignature
        # print("debug signature=%s" % curHeaders["signature"])
 
 
        self.crawl(ParentChildReadingUrl,
            method="POST",
            # data=jcJsonDict,
            data= jcJsonDictStr,
            callback=self.getParentChildReadingCallback,
            headers=curHeaders,
            save=parentChildReadingParamDict
        )
 
 
    def generateSignature(self, timestampInt, jValueParamStr):
        # print("generateSignature: timestampInt=%d, jValueParamStr=%s" % (timestampInt, jValueParamStr))
        # userId = "1134723"
        userId = gUserId
        timestamp = "%s" % timestampInt
        # localObject = "/Reading.svc/parentChildReadingBookQuery2"
        # localObject = jValueParamStr
        # userToken = "40d2267f-359e-4526-951a-66519e5868c3"
        userToken = gUserToken
        # fixedSault = “AyGt7ohMR!xx#N"
        # secretKey = “AyGt7ohMR!xx#N"
        secretKey = gSecretKey
 
 
        # strToCalc = userId + timestamp + localObject + jValueParamStr + fixedSault
        # strToCalc = timestamp + localObject + fixedSault
        strToCalc = userId + timestamp + jValueParamStr + userToken + secretKey
        # print("strToCalc=%s" % strToCalc)
 
 
        encodedStr = strToCalc.encode()
        # encodedStr = strToCalc.encode("UTF-8")
        # print("encodedStr=%s" % encodedStr)
        md5Result = md5(encodedStr)
        # print("md5Result=%s" % md5Result) # md5Result=<md5 HASH object @ 0x1044f1df0>
 
 
        # md5Result = md5()
        # md5Result.update(strToCalc)
 
 
        # md5Digest = md5Result.digest()
        # print("md5Digest=%s" % md5Digest) #
        # print("len(md5Digest)=%s" % len(md5Digest))
 
 
        md5Hexdigest = md5Result.hexdigest()
        # print("md5Hexdigest=%s" % md5Hexdigest)
        # print("len(md5Hexdigest)=%s" % len(md5Hexdigest))
        # md5Hexdigest=c687d5dfa015246e6bdc6b3c27c2afea
        print("md5=%s from %s" % (md5Hexdigest, strToCalc))
        return md5Hexdigest
        # return md5Digest
 
 
    def extractResponseData(self, respJson):
        """
        {
            "C": 2,
            "J": "H4sIAA.......AA=",
            "M": "1001",
            "ST": null
        }
        """
        # respJson = json.loads(respJson)
        respM = respJson["M"]
        if respM != RESPONSE_OK:
            return None
        encodedStr = respJson["J"]
        decodedStr = base64.b64decode(encodedStr)
        # print("decodedStr=%s" % decodedStr)
 
 
        decompressedStr = gzip.decompress(decodedStr)
        # print("decompressedStr=%s" % decompressedStr)
        decompressedStrUnicode = decompressedStr.decode("UTF-8")
        # print("decompressedStrUnicode=%s" % decompressedStrUnicode)
        decompressedJson = json.loads(decompressedStrUnicode)
        respDataDict = decompressedJson
        return respDataDict
 
 
    def getParentChildReadingCallback(self, response):
        respUrl = response.url
        print("respUrl=%s" % respUrl)
        prevParaDict = response.save
        print("prevParaDict=%s" % prevParaDict)
        respJson = response.json
        print("respJson=%s" % respJson)
 
 
        respData = self.extractResponseData(respJson)
        print("respData=%s" % respData)
 
 
        if respData:
            bookSeriesList = respData
            for eachBookSerie in bookSeriesList:
                print("eachBookSerie=%s" % eachBookSerie)
                # self.getStorybookDetail(eachBookSerie)
 
 
            prevOffset = prevParaDict["offset"]
            limit = prevParaDict["limit"]
            print("prevOffset=%d, limit=%d" % (prevOffset, limit))
            offset = prevOffset + limit
            self.getParentChildReading(offset, limit)
        else:
            print("!!! %s return no more data: %s" % (response.url, respJson))
然后再去调用api,应该就可以返回希望的包含了J字段的json字符串了:
直到获取到最后一页的数据为止:

转载请注明:在路上 » 【已解决】小花生app中调用接口parentChildReadingBookQuery2时timestamp和signature生成的逻辑

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
87 queries in 0.476 seconds, using 22.24MB memory