Android安全之应用签名验证

有时我们需要确保一个应用就是我们想要启动的那个应用,从而确保应用间通信的安全。这话听起来有点绕,下面以一个具体的例子来说明。


假如一家公司A做了一个支付应用PayApp,包名是”com.testa.pay”。随着这个支付应用市场越做越大,他们希望将其接口开放给其他公司,以使自己的公司获得更多的现金流。


首先,想要集成PayApp的公司B需要向公司A进行注册,注册成功后A公司分配给公司B三个东西,一个独一无二的ID,一个public key,一个private key。这三个信息都是敏感的,不能泄露给公司A和公司B之外的其他公司。


当第三方应用想要调用PayApp时,需要传入上面分配的三个参数传递给PayApp,PayApp在跟A公司的支付网关进行交互,然后将支付结果递交给调用者。


假如此时,有一个***开发了一个恶意应用,并且将包名取得跟PayApp一样。然后他将PayApp从手机上删除,装上自己的应用,那么当公司B的应用再次调用支付接口时,就把所有的信息传递给了***自己的应用!


公司A为了防止这种情况发生,专门开发了一个支付sdk,名字叫“PaySdk”,该sdk在调用PayApp前会对PayApp的签名进行校验,当校验通过时,才会将数据传递给PayApp。然后,所有想要调用PayApp接口的第三方应用,都需要继承支付sdk。这样就能防止信息被窃取。


下面详细介绍签名验证的具体过程:


首先,我们需要对android应用签名有一个基本了解:

1. 任何安装到android设备上的应用都已经被签名,即使debug状态的app也已经用debug的keystore签过名。

2. 需要发布的应用一定要用release的keystore进行签名。

3. 一个公司的release keystore只应该自己使用,不能泄露给其他人活公司。

4. 一个应用可能有多个签名。


关于如何生成keystore,并且给一个应用签名,可以查看官网教程:http://developer.android.com/intl/zh-cn/tools/publishing/app-signing.html


我们是通过对比签名的hash值来确保其完整性的,所以在此之前,我们应该获取release keystore对应的签名的hash值,这样在程序运行时才能进行比对。关于签名的hash值,我们可以通过下面的代码来获取:



PackageInfo pkgInfo;

try {

    pkgInfo = mActivityContext.getPackageManager().getPackageInfo(targetPkg, PackageManager.GET_SIGNATURES);

} catch (PackageManager.NameNotFoundException e) {

    Toast.makeText(mActivityContext, "The target package is not found!", Toast.LENGTH_SHORT).show();

    e.printStackTrace();

    return;

}

for (Signature signature : pkgInfo.signatures) {

    try {

        Log.i(TAG, “hash: " + Base64.encodeToString(MessageDigest.getInstance("SHA").digest(signature.toByteArray()), Base64.NO_WRAP));

    } catch (NoSuchAlgorithmException e) {

        e.printStackTrace();

    }

}



上面代码中,”targetPkg”应该被替换为你想要获取的应用的包名。代码运行后,log输出如下:


04-28 13:30:34.259 1123-1123/com.zlsam.signchecher I/MainActivity: hash: 6qX30I5Kpx4agIeolKla75oO+zA=


这里我们的PaySdk需要将“hash:”后面的那个字符串记录下来,本例中是“6qX30I5Kpx4agIeolKla75oO+zA=”。一个应用可能有多个签名,因此,我们需要记录和比对所有的签名。


然后,当第三方应用通过PaySdk调用支付时,PaySdk首先通过上面代码获取应用包”com.testa.pay”的所有签名hash,然后检查这些动态获取的hash值是否都在之前记录下来的hash当中。如果是,那么验证通过,否则验证失败。