android 安全讲座第一层 Android APK 签名比对,防止软件被破解使用

以下内容从

http://blog.csdn.net/wenlin56/article/details/8153301

这个博客转载过来,由于原理很简单,不用说什么东西,但是为了服务各位看官,自己写了一个例子供大家参考,有源码例子工程理解起来更方便...


客户端软件发布后,可能会被他人破解使用。破解的目的可能是汉化、去广告、加入便捷功能,但也有可能是植入恶意模块。Androd 也存在这个问题。


Android 应用程序在发布前,都必须被签名。假设用于签名的密钥不被泄露或共享,我们可以认为,当被破解的Android软件重新打包后,其 “签名”必然与官方版本不同。

这个“签名”到底是什么东西?
如果你手头方便的话,可以随便解压一个APK。打开解压目录,“META-INF”里面就是签名内容保存的地方。 签名过程的源码在这里,核心代码从(467行开始)。

首先,签名程序将遍历APK里的所有文件,并用SHA1算法得出每个文件的“摘要信息” 。这些摘要信息就通通记录在“META-INF”目录的“MANIFEST.MF”文件中。(第170行)
所以得出解决方案一:可以比对MANIFEST.MF里各个摘要信息是否相同,来获知APK是否被修改。(比对样本是“官方”APK,下同)

真正的“签名”却还没开始,因为还没有用到公/私钥。MANIFEST.MF的生成,提供了用密钥进行签名的清单。
接下来,会对MANIFEST.MF里所有文件的“摘要信息”用私钥进行二次加密,采用的加密算法是“RSA”非对称加密算法。生成的信息记录在目录下".SF"结尾的文件里。(第475行,第259行)
和解决方案一相同,我们同样可以比对此文件,获知APK是否被篡改。姑且不占用“解决方案二”这个名头,继续往下分析。

最后,将公钥用 PKCS#7 算法加密保存在 “.RSA” 结尾的文件中(准确说是将包含了公钥等其它相关信息的数字证书加密保存。关于公/私钥、数字证书我搜了一篇文章)。这个文件如果直接用文本阅读器打开,会看到乱码。(第483行,第305行)
解决方案二:通过判断公钥是否一致,来获知软件是否被重新“打包”。

解决方案二看起来更简单一些:
1. 方案一,需要逐一比对apk里所有文件的摘要信息。
2. 软件正常升级后,apk里面的内容必然改变。因此方案一的比对只能在同版本之间进行。

OK。方案一的具体实现抛却不谈,因为有这个思路后,再没有任何技术困难。
方案二,需要突破的地方是:如何才能解码 “.RSA” 文件,从而从中提取出公钥呢?

这里需要引入另一个源码文件:PackageParser.java。 核心代码是 collectCertificates 方法,能清楚看到通过数字证书解析公钥的过程。
但是这个类被加上了“hide”注解,所以并不能直接调用,但可以用反射去使用这些隐藏的API。
最后得出方法:

 
 
[java] view plain copy print ?
  1. // 得到任意apk公钥信息的md5字符串

  2. publicstatic String getApkSignatureMD5(String apkPath) throws Exception {

  3. Class clazz = Class.forName("android.content.pm.PackageParser");

  4. Method parsePackageMethod = clazz.getMethod("parsePackage", File.class, String.class, DisplayMetrics.class, int.class);


  5. Object packageParser = clazz.getConstructor(String.class).newInstance("");

  6. Object packag = parsePackageMethod.invoke(packageParser, new File(apkPath), null, getContext().getResources().getDisplayMetrics(), 0x0004);


  7. Method collectCertificatesMethod = clazz.getMethod("collectCertificates", Class.forName("android.content.pm.PackageParser$Package"), int.class);

  8. collectCertificatesMethod.invoke(packageParser, packag, PackageManager.GET_SIGNATURES);

  9. Signature mSignatures[] = (Signature[]) packag.getClass().getField("mSignatures").get(packag);


  10. Signature apkSignature = mSignatures.length > 0 ? mSignatures[0] : null;


  11. if(apkSignature != null) {

  12. // 说明:没有提供md5的具体实现

  13. return StringUtils.md5(apkSignature.toCharsString());

  14. }


  15. returnnull;

  16. }

// 得到任意apk公钥信息的md5字符串
public static String getApkSignatureMD5(String apkPath) throws Exception {
	Class clazz = Class.forName("android.content.pm.PackageParser");
	Method parsePackageMethod = clazz.getMethod("parsePackage", File.class, String.class, DisplayMetrics.class, int.class);

	Object packageParser = clazz.getConstructor(String.class).newInstance("");
	Object packag = parsePackageMethod.invoke(packageParser, new File(apkPath), null, getContext().getResources().getDisplayMetrics(), 0x0004);

	Method collectCertificatesMethod = clazz.getMethod("collectCertificates", Class.forName("android.content.pm.PackageParser$Package"), int.class);
	collectCertificatesMethod.invoke(packageParser, packag, PackageManager.GET_SIGNATURES);
	Signature mSignatures[] = (Signature[]) packag.getClass().getField("mSignatures").get(packag);

	Signature apkSignature = mSignatures.length > 0 ? mSignatures[0] : null;

	if(apkSignature != null) {
                // 说明:没有提供md5的具体实现
		return StringUtils.md5(apkSignature.toCharsString());
	}

	return null;
}
 
 

如果当前运行的软件要得到自身的公钥信息,只需要得到apk位置就可以了(每个软件(非内置)安装后,都会在/data/system里保存一份apk文件):

[java] view plain copy print ?
  1. String path = context.getApplicationInfo()

  2. .publicSourceDir

String path = context.getApplicationInfo()
                     .publicSourceDir


接下来的事情就是找到比对时机。什么时候,采取什么形式来比对。
建议可以在一些关键步骤(如程序第一次启动、安装成功、登陆/注册等)启动比对。
若是b/s结构的程序,建议将比对放在服务器进行。也可以选择放在Native代码里进行比对(但是也更容易在破解后通过修改代码绕过)。

本文若有不周详之处,欢迎读者指正。


你可能感兴趣的:(android)