最近想爬虎扑的全量用户,初步打算从 App 端入手,不出意外,在请求中依旧碰到了加密参数。

请求参数

ios 端和 android 端的请求参数大同小异,这里以 android 端的请求参数为例,

clientId_ssid_imeiclientandroid_id这几个是设备和客户端连接相关信息
crtstamp时间戳,不过位数不同
其他几个参数很好理解,只有一个sign是加密字段,每个请求都会重新计算生成

获取源码

反编译流程可以参考之前的文章链家App反编译破解加密字段,然后使用 JD-GUI 查看源码。

sign 生成逻辑

1
2
# 这里将请求参数传给了函数b
paramOkRequestParams.put("sign", q.b(paramOkRequestParams));

1
2
3
4
5
# 函数bgetSortURL顾名思义对url排序
public static String b(OkRequestParams paramOkRequestParams)
{
  return paramOkRequestParams.getSortURL();
}

 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
# 参数排序后加盐传给函数a
public String getSortURL()
{
  Object localObject = new ArrayList(this.stringParams.entrySet());
  Collections.sort((List)localObject, new Comparator()
  {
    public int a(Map.Entry<String, String> paramAnonymousEntry1, Map.Entry<String, String> paramAnonymousEntry2)
    {
      return ((String)paramAnonymousEntry1.getKey()).toString().compareTo(((String)paramAnonymousEntry2.getKey()).toString());
    }
  });
  new JSONObject(this.stringParams);
  if (((Integer)c.a("basic_signature", Integer.valueOf(0), Integer.class)).intValue() == 0) {}
  StringBuilder localStringBuilder = new StringBuilder();
  localObject = ((List)localObject).iterator();
  while (((Iterator)localObject).hasNext())
  {
    Map.Entry localEntry = (Map.Entry)((Iterator)localObject).next();
    if (localStringBuilder.length() > 0) {
      localStringBuilder.append("&");
    }
    localStringBuilder.append((String)localEntry.getKey());
    localStringBuilder.append("=");
    localStringBuilder.append((String)localEntry.getValue());
  }
  return w.a(localStringBuilder.toString() + "HUPU_SALT_AKJfoiwer394Jeiow4u309");
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 函数a猜测这里是使用了加密算法未知点在于bc
public static String a(String paramString)
{
  int i = 0;
  b.reset();
  b.update(paramString.getBytes());
  paramString = b.digest();
  c.setLength(0);
  while (i < paramString.length)
  {
    int j = paramString[i] & 0xFF;
    if (j < 16) {
      c.append('0');
    }
    c.append(Integer.toHexString(j));
    i += 1;
  }
  return c.toString();
}

1
2
3
4
5
6
# b对应的是md5加密
private static MessageDigest b;
private static StringBuilder c;
···
b = MessageDigest.getInstance("MD5");
c = new StringBuilder();

综上,sign的生成逻辑:

  • url中的params转字典
  • 根据key排序
  • 排序后拼接,k1=v1&k2=v2 这样的格式
  • 拼接后字符串加盐 HUPU_SALT_AKJfoiwer394Jeiow4u309
  • md5加密

python版本:

1
2
3
4
5
6
def get_sign(params_dict):
    sorted_dict = {key: params_dict[key] for key in sorted(params_dict)}
    params_str = '&'.join([key + '=' + str(sorted_dict[key]) for key in sorted_dict.keys()])
    params_str = params_str + 'HUPU_SALT_AKJfoiwer394Jeiow4u309'
    sign = hashlib.md5(params_str.encode()).hexdigest()
    return sign