折腾:
【已解决】小花生app中调用接口parentChildReadingBookQuery2时timestamp和signature生成的逻辑
期间,需要把找到的java的md5参数加密的代码:
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 (((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); } |
其中调用的代码是:
com/huili/readingclub/network/XutilsHttpClient.java
1 2 3 4 5 | if (paramHttpMethod == HttpRequest.HttpMethod.GET) { ModelSecurity.addSignature(localRequestParams, paramString1); } else if (paramHttpMethod == HttpRequest.HttpMethod.POST) { ModelSecurity.addSignature(localRequestParams, (String)JsonUtil.jsonToMap(paramString2).get( "J" )); } |
想办法改造成python的。
并且,找到一组数据,供测试和验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | POST / Reading.svc / parentChildReadingBookQuery2 HTTP / 1.1 Content - Type : application / json Authorization: NSTp9~)NwSfrXp@\ userId: 1134723 timestamp: 1553845899 signature: c687d5dfa015246e6bdc6b3c27c2afea Content - Length: 212 Host: www.xiaohuasheng.cn: 83 User - Agent: 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 Cookie: ASP.NET_SessionId = sjwpqfwt013elkehr2ytl0sq Cookie2: $Version = 1 Accept - Encoding: gzip Connection: keep - alive { "J" : "{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":20,\"limit\":10}" , "C" : 0 } |
其中有
J字段值
timestamp值
-》算出的signature是c687d5dfa015246e6bdc6b3c27c2afea
需要用Python复原此过程
然后就是想办法去写Python代码了。
除了写代码还,还要分析对应逻辑。
此处简单看了下,看来只需要关注:
1 2 3 4 5 6 7 | 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()); |
java substring
而其中:
- MainActivity.userId:应该就是字符串”1134723″
- l:此处的时间戳
- long l = DateUtils.get1970ToNowSeconds();
- 此处直接赋值用于测试
- localObject:
- if (paramString.startsWith(“http://www.xiaohuasheng.cn:83”)) {
- localObject = paramString.substring(“http://www.xiaohuasheng.cn:83”.length());
- }
- -》看来是获取
- http://www.xiaohuasheng.cn:83/Reading.svc/parentChildReadingBookQuery2
- 中的:
- /Reading.svc/parentChildReadingBookQuery2
- paramString:此处传入的J的值
- 固定字符串:”AyGt7ohMR!xxx#N”
然后去计算出md5值
再去看看:
StringUtil.md5
com/huili/readingclub/utils/StringUtil.java
1 2 3 4 | public static String md5(String paramString) { return Const.getMD5Str(paramString); } |
com/huili/readingclub/utils/Const.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 52 53 54 55 56 57 58 59 60 61 | public static final String getMD5Str(String paramString) { char[] arrayOfChar = new char[ 16 ]; char[] tmp6_5 = arrayOfChar; tmp6_5[ 0 ] = 48 ; char[] tmp12_6 = tmp6_5; tmp12_6[ 1 ] = 49 ; char[] tmp18_12 = tmp12_6; tmp18_12[ 2 ] = 50 ; char[] tmp24_18 = tmp18_12; tmp24_18[ 3 ] = 51 ; char[] tmp30_24 = tmp24_18; tmp30_24[ 4 ] = 52 ; char[] tmp36_30 = tmp30_24; tmp36_30[ 5 ] = 53 ; char[] tmp42_36 = tmp36_30; tmp42_36[ 6 ] = 54 ; char[] tmp49_42 = tmp42_36; tmp49_42[ 7 ] = 55 ; char[] tmp56_49 = tmp49_42; tmp56_49[ 8 ] = 56 ; char[] tmp63_56 = tmp56_49; tmp63_56[ 9 ] = 57 ; char[] tmp70_63 = tmp63_56; tmp70_63[ 10 ] = 97 ; char[] tmp77_70 = tmp70_63; tmp77_70[ 11 ] = 98 ; char[] tmp84_77 = tmp77_70; tmp84_77[ 12 ] = 99 ; char[] tmp91_84 = tmp84_77; tmp91_84[ 13 ] = 100 ; char[] tmp98_91 = tmp91_84; tmp98_91[ 14 ] = 101 ; char[] tmp105_98 = tmp98_91; tmp105_98[ 15 ] = 102 ; tmp105_98; try { paramString = paramString.getBytes(); Object localObject = MessageDigest.getInstance( "MD5" ); ((MessageDigest)localObject).update(paramString); paramString = ((MessageDigest)localObject).digest(); int i = paramString.length; localObject = new char[i * 2 ]; int j = 0 ; int k = 0 ; while (j < i) { int m = paramString[j]; int n = k + 1 ; localObject[k] = ((char)arrayOfChar[(m >> 4 & 0xF )]); k = n + 1 ; localObject[n] = ((char)arrayOfChar[(m & 0xF )]); j + + ; } paramString = new String((char[])localObject); return paramString; } catch (Exception paramString) {} return null; } |
-》看起来,以及,希望是,普通的,md5加密算法
-》去找找python中的md5:
【已解决】Python中计算字符串的md5值
结果不对:
【已解决】为何Python中32字节的md值和小花生中getMD5Str计算出的md5值不同
期间去:
【已解决】小花生中如何得到getToken的计算逻辑以便得到正确的md5值可以正常请求接口
此处得到了user的token:
- userId是:1554276832
- 返回的token是:40d2267f-359e-4526-951a-66519e5868c3
接着再去试试模拟addSignature中的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 | from hashlib import md5 def generateSignature(timestampInt, paramString): userId = "1134723" timestamp = "%s" % timestampInt # localObject = "/Reading.svc/parentChildReadingBookQuery2" # localObject = paramString userToken = "40d2267f-359e-4526-951a-66519e5868c3" # fixedSault = “AyGt7ohMR!xx#N" secretKey = “AyGt7ohMR!xx #N" # strToCalc = userId + timestamp + localObject + paramString + fixedSault # strToCalc = timestamp + localObject + fixedSault strToCalc = userId + timestamp + paramString + 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=585ad2765d147c7e918478a4ce843ed2 # md5Hexdigest=321ed614f7211147be90a45bbca94127 # md5Hexdigest=defc3b231167e16932ea923d616e2d40 # md5Hexdigest=c687d5dfa015246e6bdc6b3c27c2afea return md5Hexdigest # return md5Digest if __name__ = = "__main__" : timestampInt = 1553845899 # paramString = "{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":20,\"limit\":10}" paramString = """{"userId":"1134723","fieldName":"","fieldValue":"全部类别","theStageOfTheChild":"","parentalEnglishLevel":"","supportingResources":"有音频","offset":20,"limit":10}""" generatedSignature = generateSignature(timestampInt, paramString) # print("timestampInt=%d, paramString=%s-> %s" % (timestampInt, paramString, generatedSignature)) |
即:
1 | 11347231553845899{ "userId" : "1134723" , "fieldName" : "" , "fieldValue" : "全部类别" , "theStageOfTheChild" : "" , "parentalEnglishLevel" : "" , "supportingResources" : "有音频" , "offset" :20, "limit" :10}40d2267f-359e-4526-951a-66519e5868c3AyGt7ohMR!xxx #N |
算出的md5是:
1 | c687d5dfa015246e6bdc6b3c27c2afea |
和最开始抓包抓到的:
1 | signature: c687d5dfa015246e6bdc6b3c27c2afea |
是一致的了-》终于计算出正确的几个参数合并后的md5的signature值了。
【总结】
此处对于之前
src/main/java/com/huili/readingclub/model/ModelSecurity.java
的addSignature:
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 ); } } |
中的md5核心计算逻辑:
1 2 3 4 5 6 7 | 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()); |
对应的Python实现是:
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 | from hashlib import md5 def generateSignature(timestampInt, paramString): userId = "1134723" timestamp = "%s" % timestampInt # localObject = "/Reading.svc/parentChildReadingBookQuery2" # localObject = paramString userToken = "40d2267f-359e-4526-951a-66519e5868c3" # fixedSault = “AyGt7ohMR!xx#N" secretKey = “AyGt7ohMR!xx #N" # strToCalc = userId + timestamp + localObject + paramString + fixedSault # strToCalc = timestamp + localObject + fixedSault strToCalc = userId + timestamp + paramString + 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 return md5Hexdigest # return md5Digest if __name__ = = "__main__" : timestampInt = 1553845899 paramString = "{\"userId\":\"1134723\",\"fieldName\":\"\",\"fieldValue\":\"全部类别\",\"theStageOfTheChild\":\"\",\"parentalEnglishLevel\":\"\",\"supportingResources\":\"有音频\",\"offset\":20,\"limit\":10}" # paramString = """{"userId":"1134723","fieldName":"","fieldValue":"全部类别","theStageOfTheChild":"","parentalEnglishLevel":"","supportingResources":"有音频","offset":20,"limit":10}""" generatedSignature = generateSignature(timestampInt, paramString) # print("timestampInt=%d, paramString=%s-> %s" % (timestampInt, paramString, generatedSignature)) |
运行效果:

要计算的逻辑
1 | userId + timestamp + paramString + userToken + secretKey |
对应的字符串
1 | 11347231553845899{ "userId" : "1134723" , "fieldName" : "" , "fieldValue" : "全部类别" , "theStageOfTheChild" : "" , "parentalEnglishLevel" : "" , "supportingResources" : "有音频" , "offset" :20, "limit" :10}40d2267f-359e-4526-951a-66519e5868c3AyGt7ohMR!xxx #N |
的md5值是:
c687d5dfa015246e6bdc6b3c27c2afea
另外去网上验证:
也是对的:

【后记20190410】
后来发现,源码中:

对于GET来说,参数还略有不同,原先的paramString其实就从POST的jValue变成了urlEndpoint了。
所以最后代码改为:
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 | def generateSignature( self , timestampInt, jValueOrUrlEndpoint): # print("generateSignature: timestampInt=%d, jValueOrUrlEndpoint=%s" % (timestampInt, jValueOrUrlEndpoint)) # userId = "1134723" userId = gUserId timestamp = "%s" % timestampInt # localObject = "/Reading.svc/parentChildReadingBookQuery2" # localObject = jValueOrUrlEndpoint # userToken = "40d2267f-359e-4526-951a-66519e5868c3" userToken = gUserToken # fixedSault = “AyGt7ohMR!xx#N" # secretKey = “AyGt7ohMR!xx#N" secretKey = gSecretKey # strToCalc = userId + timestamp + localObject + jValueOrUrlEndpoint + fixedSault # strToCalc = timestamp + localObject + fixedSault strToCalc = userId + timestamp + jValueOrUrlEndpoint + 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 generateCurrentHeaders( self , jValueOrUrlEndpoint): curHeaders = copy.deepcopy(gHeaders) curTimestampInt = getCurTimestamp() curTimestampStr = str (curTimestampInt) curHeaders[ "timestamp" ] = curTimestampStr curSignature = self .generateSignature(curTimestampInt, jValueOrUrlEndpoint) curHeaders[ "signature" ] = curSignature return curHeaders urlEndpoint = "/Reading.svc/viewEnglishSeries2/%s/%s" % (gUserId, seriePrimayKey) fullUrl = "%s%s" % (gServerPort, urlEndpoint) # /Reading.svc/viewEnglishSeries2/1134723/31 curHeaders = self .generateCurrentHeaders(urlEndpoint) |
从而也支持GET类的api请求的signature参数的生成。