有时我们需要确保一个应用就是我们想要启动的那个应用,从而确保应用间通信的安全。这话听起来有点绕,下面以一个具体的例子来说明。
假如一家公司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当中。如果是,那么验证通过,否则验证失败。