文章目录

  • 1、反汇编豆瓣APP
  • 2、定位签名计算位置
  • 3、获取豆瓣APP的签名
  • 4、HMAC Hash加密逻辑分析
  • 5、代码实现
  • 6、注意事项

完整工程上传到了GitHub上,仅限于研究使用,欢迎star,如不能运行请看注意事项

项目地址:https://github.com/bestyize/DoubanAPI


1、反汇编豆瓣APP

我们用强大的jadx来反汇编豆瓣app

选择文件-打开。然后找到豆瓣app的安装包后打开。

2、定位签名计算位置

点击搜索图标,我们搜索一下在上一节找的_sig是在哪里组装的


双击进去,可以看到一个叫做ApiSignatureHelper.a的方法获得了_sig的值

Pair<String, String> a2 = ApiSignatureHelper.a(request);


再点进去看看,可以看到这个类的实现非常简单,Pair是安卓里面的一个只有两个值的数据结构,ApiSignatureHelper.a的作用就是计算_sig的值。


public class ApiSignatureHelper {static Pair<String, String> a(Request request) {if (request == null) {return null;}String header = request.header(com.douban.push.internal.api.Request.HEADER_AUTHORIZATION);if (!TextUtils.isEmpty(header)) {header = header.substring(7);}return a(request.url().toString(), request.method(), header);}public static Pair<String, String> a(String str, String str2, String str3) {String decode;if (TextUtils.isEmpty(str)) {return null;}String str4 = FrodoApi.a().e.b;if (TextUtils.isEmpty(str4)) {return null;}StringBuilder sb = new StringBuilder();sb.append(str2);String encodedPath = HttpUrl.parse(str).encodedPath();if (encodedPath == null || (decode = Uri.decode(encodedPath)) == null) {return null;}if (decode.endsWith("/")) {decode = decode.substring(0, decode.length() - 1);}sb.append(StringPool.AMPERSAND);sb.append(Uri.encode(decode));if (!TextUtils.isEmpty(str3)) {sb.append(StringPool.AMPERSAND);sb.append(str3);}long currentTimeMillis = System.currentTimeMillis() / 1000;sb.append(StringPool.AMPERSAND);sb.append(currentTimeMillis);return new Pair<>(HMACHash1.a(str4, sb.toString()), String.valueOf(currentTimeMillis));}
}

看完代码后我们可以知道,最后是使用了HMAC Hash算法,把str4作为key,把sb.toString()作为加密内容进行的加密。

public class HMACHash1 {public static final String a(String str, String str2) {try {SecretKeySpec secretKeySpec = new SecretKeySpec(str.getBytes(), LiveHelper.HMAC_SHA1);Mac instance = Mac.getInstance(LiveHelper.HMAC_SHA1);instance.init(secretKeySpec);return Base64.encodeToString(instance.doFinal(str2.getBytes()), 2);} catch (Exception e) {e.printStackTrace();return null;}}
}

但是由于HMAC Hash是一个不可逆的加密算法,我们是不能根据_sig来反推加密密钥的。

所以我们能做的就是直接获取这个加密密钥。

我们追一下str4的来源:

String str4 = FrodoApi.a().e.b;


可以清晰地看到,这个值是ZenoConfig在构造函数初始化时候传入的,是第三个参数。
我们再追究下哪里调用了这个构造函数。


可以看到,这个值是从这里来的

String d2 = FrodoUtils.d();

我们再追踪一下


在这里,我们终于到了计算值密钥的位置。

@SuppressLint({"PackageManagerGetSignatures"})public static void a(boolean z) {if (TextUtils.isEmpty(b)) {b = "74CwfJd4+7LYgFhXi1cx0IQC35UQqYVFycCE+EVyw1E=";}if (TextUtils.isEmpty(c)) {c = "bHUvfbiVZUmm2sQRKwiAcw==";}if (z) {try {String encodeToString = Base64.encodeToString(AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray(), 0);b = AES.a(b, encodeToString);c = AES.a(c, encodeToString);} catch (PackageManager.NameNotFoundException e2) {e2.printStackTrace();}}}

在这段代码中,将c作为密文

c = "bHUvfbiVZUmm2sQRKwiAcw=="

将apk签名作为密钥,经过AES加密得到最终的加密密钥,作为前面提到的HMAC Hash算法中的加密密钥。

 String encodeToString = Base64.encodeToString(AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray(), 0);

3、获取豆瓣APP的签名

在上一节中,我们定位到了计算HMAC Hash算法密钥的位置,这个位置是由AES加密获取到一个结果,作为HMAC Hash算法密钥的,但是AES加密的文本我们可以直接找到,就是

bHUvfbiVZUmm2sQRKwiAcw==

但我们还不知道AES加密密钥是什么。熟悉安卓开发的人应该知道,这句话是用来获取当前应用的签名的,这是安卓的一种防篡改的安全机制。只要我们修改了包,签名就会变化,所以,我们不能直接修改豆瓣APP的安装包。

AppContext.a().getPackageManager().getPackageInfo(AppContext.a().getPackageName(), 64).signatures[0].toByteArray()

不过,其他应用也可以获取已安装应用的签名信息,只需要把对应app的包名填入即可。

Application application=(Application)getApplicationContext();
PackageInfo packageInfo=application.getPackageManager().getPackageInfo("com.douban.frodo",PackageManager.GET_SIGNATURES);String sign=Base64.encodeToString(packageInfo.signatures[0].toByteArray(),0);

这样我们就获取到了我们需要的字串

    public final static String SIGN="MIICUjCCAbsCBEty1MMwDQYJKoZIhvcNAQEEBQAwcDELMAkGA1UEBhMCemgxEDAOBgNVBAgTB0Jl\n" +"aWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEzARBgNVBAoTCkRvdWJhbiBJbmMxFDASBgNVBAsTC0Rv\n" +"dWJhbiBJbmMuMRIwEAYDVQQDEwlCZWFyIFR1bmcwHhcNMTAwMjEwMTU0NjExWhcNMzcwNjI3MTU0\n" +"NjExWjBwMQswCQYDVQQGEwJ6aDEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzET\n" +"MBEGA1UEChMKRG91YmFuIEluYzEUMBIGA1UECxMLRG91YmFuIEluYy4xEjAQBgNVBAMTCUJlYXIg\n" +"VHVuZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAg622fxLuwQtC8KLYp5gHk0OmfrFiIisz\n" +"kzPLBhKPZDHjYS1URhQpzf00T8qg2oEwJPPELjN2Q7YOoax8UINXLhMgFQkyAvMfjdEOSfoKH93p\n" +"v2d4n/IjQc/TaDKu6yb53DOq76HTUYLcfLKOXaGwGjAp3QqTqP9LnjJjGZCdSvMCAwEAATANBgkq\n" +"hkiG9w0BAQQFAAOBgQA3MovcB3Hv4bai7OYHU+gZcGQ/8sOLAXGD/roWPX3gm9tyERpGztveH35p\n" +"aI3BrUWg2Vir0DRjbR48b2HxQidQTVIH/HOJHV0jgYNDviD18/cBwKuLiBvdzc2Fte+zT0nnHXMy\n" +"E6tVeW3UdHC1UvzyB7Qcxiu4sBiEO1koToQTWw==\n";

不过,这个加密密钥其实是固定的,我们直接把jadx反编译后的代码,移植到这里,计算出这个加密密钥,以后就不需要再重复计算了,最后,我们得到的结果是

bf7dddc7c9cfe6f7

这就是HMAC Hash算法需要的加密密钥

4、HMAC Hash加密逻辑分析

在得到HAMC Hash的加密密钥之后,我们再看一下,被HMAC Hash算法加密的字符串是怎么得到的。

    //str:API的地址,不包括后面参数,举例:str="https://frodo.douban.com/api/v2/elessar/subject/27260217/photos"//str2:请求方法,这里是GET,举例:str2="GET"//str3: str3=null;public static Pair<String, String> a(String str, String str2, String str3) {String decode;if (TextUtils.isEmpty(str)) {return null;}String str4 = FrodoApi.a().e.b;//HMAC Hash密钥,在前面我们得到的结果是:bf7dddc7c9cfe6f7if (TextUtils.isEmpty(str4)) {return null;}StringBuilder sb = new StringBuilder();sb.append(str2);String encodedPath = HttpUrl.parse(str).encodedPath();if (encodedPath == null || (decode = Uri.decode(encodedPath)) == null) {return null;}if (decode.endsWith("/")) {decode = decode.substring(0, decode.length() - 1);}sb.append(StringPool.AMPERSAND);sb.append(Uri.encode(decode));if (!TextUtils.isEmpty(str3)) {sb.append(StringPool.AMPERSAND);sb.append(str3);}long currentTimeMillis = System.currentTimeMillis() / 1000;//当前时间,取秒,也被当作被加密的内容了sb.append(StringPool.AMPERSAND);sb.append(currentTimeMillis);return new Pair<>(HMACHash1.a(str4, sb.toString()), String.valueOf(currentTimeMillis));}

至此,豆瓣的加密算法分析完成,接下来就是实现它

5、代码实现

在上面分析的代码中,有一些是安卓特有的API,但是为了让程序能run everywhere,我对其中的一些数据结构做了替换,对一些API进行了移植(感谢安卓是开源的)

由于数据结构Pair仅仅是Android中的一个类,所以,为了在别的地方用Java的地方也能用,我们可以移植,也可以用hashMap代替

public class SignatureHelper {private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();private static final String DEFAULT_ENCODING = "UTF-8";public static final String AMPERSAND = "&";private final static int NOT_FOUND = -1;
//    public final static String SIGN="MIICUjCCAbsCBEty1MMwDQYJKoZIhvcNAQEEBQAwcDELMAkGA1UEBhMCemgxEDAOBgNVBAgTB0Jl\n" +
//            "aWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEzARBgNVBAoTCkRvdWJhbiBJbmMxFDASBgNVBAsTC0Rv\n" +
//            "dWJhbiBJbmMuMRIwEAYDVQQDEwlCZWFyIFR1bmcwHhcNMTAwMjEwMTU0NjExWhcNMzcwNjI3MTU0\n" +
//            "NjExWjBwMQswCQYDVQQGEwJ6aDEQMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzET\n" +
//            "MBEGA1UEChMKRG91YmFuIEluYzEUMBIGA1UECxMLRG91YmFuIEluYy4xEjAQBgNVBAMTCUJlYXIg\n" +
//            "VHVuZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAg622fxLuwQtC8KLYp5gHk0OmfrFiIisz\n" +
//            "kzPLBhKPZDHjYS1URhQpzf00T8qg2oEwJPPELjN2Q7YOoax8UINXLhMgFQkyAvMfjdEOSfoKH93p\n" +
//            "v2d4n/IjQc/TaDKu6yb53DOq76HTUYLcfLKOXaGwGjAp3QqTqP9LnjJjGZCdSvMCAwEAATANBgkq\n" +
//            "hkiG9w0BAQQFAAOBgQA3MovcB3Hv4bai7OYHU+gZcGQ/8sOLAXGD/roWPX3gm9tyERpGztveH35p\n" +
//            "aI3BrUWg2Vir0DRjbR48b2HxQidQTVIH/HOJHV0jgYNDviD18/cBwKuLiBvdzc2Fte+zT0nnHXMy\n" +
//            "E6tVeW3UdHC1UvzyB7Qcxiu4sBiEO1koToQTWw==\n";public static Map<String, String> getVerifyMap(String str, String str2, String str3) {Map<String, String> map=new HashMap<>();String decode;if (TextUtils.isEmpty(str)) {return null;}String str4 = "bf7dddc7c9cfe6f7";if (TextUtils.isEmpty(str4)) {return null;}StringBuilder sb = new StringBuilder();sb.append(str2);String encodedPath = encodedPath(str);System.out.println(encodedPath);if (encodedPath == null || (decode = encodedPath) == null) {return null;}if (decode.endsWith("/")) {decode = decode.substring(0, decode.length() - 1);}sb.append(AMPERSAND);sb.append(uriEncode(decode,null));if (!TextUtils.isEmpty(str3)) {sb.append(AMPERSAND);sb.append(str3);}long currentTimeMillis = System.currentTimeMillis() / 1000;sb.append(AMPERSAND);sb.append(currentTimeMillis);try {map.put("_sig", URLEncoder.encode(HMACHash1.a(str4, sb.toString()),"utf-8"));} catch (Exception e) {e.printStackTrace();}map.put("_ts",String.valueOf(currentTimeMillis));return map;}public static String uriEncode(String s, String allow) {if (s == null) {return null;}StringBuilder encoded = null;int oldLength = s.length();int current = 0;while (current < oldLength) {int nextToEncode = current;while (nextToEncode < oldLength&& isAllowed(s.charAt(nextToEncode), allow)) {nextToEncode++;}if (nextToEncode == oldLength) {if (current == 0) {// We didn't need to encode anything!return s;} else {// Presumably, we've already done some encoding.encoded.append(s, current, oldLength);return encoded.toString();}}if (encoded == null) {encoded = new StringBuilder();}if (nextToEncode > current) {// Append allowed characters leading up to this point.encoded.append(s, current, nextToEncode);} else {// assert nextToEncode == current}current = nextToEncode;int nextAllowed = current + 1;while (nextAllowed < oldLength&& !isAllowed(s.charAt(nextAllowed), allow)) {nextAllowed++;}String toEncode = s.substring(current, nextAllowed);try {byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);int bytesLength = bytes.length;for (int i = 0; i < bytesLength; i++) {encoded.append('%');encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);encoded.append(HEX_DIGITS[bytes[i] & 0xf]);}} catch (UnsupportedEncodingException e) {throw new AssertionError(e);}current = nextAllowed;}return encoded == null ? s : encoded.toString();}private static boolean isAllowed(char c, String allow) {return (c >= 'A' && c <= 'Z')|| (c >= 'a' && c <= 'z')|| (c >= '0' && c <= '9')|| "_-!.~'()*".indexOf(c) != NOT_FOUND|| (allow != null && allow.indexOf(c) != NOT_FOUND);}public static String encodedPath(String url) {String scheme="https";int pathStart = url.indexOf('/', scheme.length() + 3); // "://".length() == 3.int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");return url.substring(pathStart, pathEnd);}public static int delimiterOffset(String input, int pos, int limit, String delimiters) {for(int i = pos; i < limit; ++i) {if (delimiters.indexOf(input.charAt(i)) != -1) {return i;}}return limit;}
}

6、注意事项

豆瓣为了防止抓包,还对UA进行了校验,在计算出正确地址后,如果想要请求API,需要把UA设置成(这里的UA也可以在fildder里面看到)

api-client/1 com.douban.frodo/6.42.2(194) Android/22 product/shamu vendor/OPPO model/OPPO R11 Plus  rom/android  network/wifi  platform/mobile nd/1

关于完整工程:

完整工程是一个servlet程序,用idea导入即可,测试部分在图示位置

安卓逆向-豆瓣app签名算法分析与解密(下)相关推荐

  1. 安卓逆向-豆瓣app签名算法分析与解密(上)

    文章目录 1.背景介绍 2.工具准备 3.Fildder抓包 3.1 配置fiddler代理 3.2 配置安卓模拟器的代理 3.3 为安卓模拟器安装证书 4.抓取豆瓣APP的网络请求 1.背景介绍 豆 ...

  2. 安卓逆向--豆瓣app签名sign

    声明 本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!由于本人水平有限,如有理解或者描述不准确的地方,还望各位大佬指教!! 前言 今天我们要分 ...

  3. Web逆向、软件逆向、安卓逆向、APP逆向,关于网络安全这些你必须懂

    逆向工程是网络安全行业里面一项很重要的技术. 先解释下逆向工程是什么. 逆向是一个相对正向而言的解释,相对正向来说,对一个程序来讲,正向就是开发的过程,从0到1. 就是在一个软件诞生的整个生命周期中的 ...

  4. 安卓逆向——修改APP的名称,图标和包名多开分身

    修改APP的名称,图标和包名多开分身 1. 把apk拖入到 Android killer 2. 修改apk的名字 搜到结果,把得到的文件,把"土豆视频" 改成 修改成的名字 回编译 ...

  5. 安卓逆向——修改APP去广告案例

    修改APP去广告案例 首先 没有去 广告打开的样子 ,显示的广告 这里使用 Android killer 工具 反编译 apk ,查看源码,打开 AndroidManifest.xml 文件查看 配置 ...

  6. 安卓逆向之APP抓包

    市面上的抓包工具很多,其中最主要的两款就是Charles以及Fiddler,下面按照步骤讲一下Fiddler抓包工具的配置以及使用过程! 1.Fiddler软件的下载 到如下网址下载Fiddler:h ...

  7. 3、xx配音狂app登陆算法分析【Android逆向分析学习】

    学习安卓逆向分析的一个小菜鸟,记录分析的"快乐时光",小白图个乐,大佬乎喷 分析的APP 英语配音狂包名:com.zhuoyue.peiyinkuang版本号:5.2.5模拟器安装 ...

  8. 安卓逆向Xposed HOOK TB直播APP的x-sign参数

    最近学习安卓逆向,接触一下TB系的APP,了解大厂APP是做数据安全的,这篇文章主要介绍某宝直播APP的签名参数x-sign的HOOK过程,当然,其他的参数也是可以HOOK的.本文只用于学习交流,请勿 ...

  9. 安卓逆向入门练习之电影天堂APP逆向分析

    准备 抓包环境及工具准备,参考:使用Fiddler对安卓App抓包 APP:电影天堂APP,版本:3.5.0 抓包 使用fiddler在模拟器里对App进行抓包,拦截到四种类型的数据: http:// ...

最新文章

  1. Linux 命令 top 学习总结
  2. iOS超全开源框架、项目和学习资料汇总:UI篇
  3. 《MongoDB权威指南》迷你书连载一-入门篇
  4. python读取指定行的txt_【Python】读取txt文件,获取指定行中指定位置数据
  5. 台湾国立大学郭彦甫Matlab教程笔记(14)polynomial differentiation多项式微分
  6. 解决Windows下Arm下Linux下Qt4程序的中文乱码问题
  7. verilog7人表决电路设计
  8. 你真的了解Python吗?这篇文章可以让你了解90%,赶紧收藏!
  9. html div转行,转行web前端开发的人有没有未来
  10. mysql事务、视图
  11. MiluGPS(迷路者GPS导航软件)
  12. html模板替换值,Go语言多值替换的HTML模板实例分析
  13. SogouLabDic搜狗词库
  14. 【秋色动人xp情侣主题】
  15. Recovery文件路径
  16. git reset --hard HEAD~X误删恢复操作
  17. 用maven的 Mybatis代码生成器
  18. 分布式记账的几种方式
  19. 横向扩展 纵向扩展 数据库_理解数据库扩展模式的指南
  20. Field brandService in cn.est.service.impl.HomeServiceImpl required a bean of type ‘cn.est.service.Br

热门文章

  1. 解决<tx:attributes>标红,元素tx:attributes不允许在这里
  2. c语言链表报错,【debug】c语言链表运行报错
  3. 与图像处理有关的书籍和论文
  4. sqlmap waf识别模块identYwaf
  5. 如何用PPT绘制设计一个分割型环形图?
  6. 嵌入式开发中的滤波器设计
  7. 初识Linux下C语言编程
  8. MATLAB模糊控制Suface三维图像导出svg高清矢量图方法
  9. Burp Suite Professional v2.1安装与使用
  10. 数字化校园建设之实践二