如何在客户端验证 APK 签名证书?

How to validate the APK signing certificate at client side?

我正在开发一个与服务器通信的 android 应用程序,我想在 运行 时验证我的应用程序自发布以来没有被修改(一个用户有修改后的应用程序应该无法登录到应用程序)。

由于修改后的应用程序签名与原始签名不同,我决定:

  1. 提取嵌入在 android 应用
  2. 中的签名 certificate/s
  3. 将它与登录请求一起发送到服务器(在客户端(即 apk)编写整个验证过程是行不通的,因为有人可以修改 apk 来绕过它。)
  4. 验证一下
  5. 如果证书有效处理登录请求/否则return错误

这是我上面数字 1 的代码:

Context context = this;
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;

try {
    packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

Signature[] signatures = packageInfo.signatures;  

所以我的问题是:

  1. 有没有更好的方法来验证apk?
  2. 如果这个方法没问题,
    2.1 发送证书会不会占用带宽?
    2.2 如何验证证书?(我在服务器端有 mykey.jks,我最初用来签署 apk)

(这也是我关于 Whosebug 的第一个问题,因此非常感谢指出我在提问时犯的任何错误!。)

首先,有人总是有可能绕过这种安全措施,例如通过硬编码传递给您的服务器的证书。你只能尽量让它变得尽可能难,但它永远不会 100% 安全。

话虽这么说,你所做的似乎还不错。请注意,在最新的 SDK 中,pm.signatures 已弃用,取而代之的是 pm.signingInfo

在大小方面,证书并不大,但要使请求更小,您可能应该只发送一个哈希值(例如 SHA256)。

为了防止人们在请求中对证书进行硬编码,您还可以考虑将另一个值与证书一起进行哈希处理,即哈希(证书 + 时间戳),并在请求中发送时间戳,以便重新计算哈希服务器端。如果时间戳距离当前日期太远,则拒绝该请求。同样,它不是完全安全的,而是增加了另一层复杂性来对您的代码进行逆向工程。您还可以添加 versionCode 并开始拒绝旧版本的请求(例如,如果您在其中一个旧版本中检测到安全漏洞)并提示这些用户更新应用程序(或在后台为他们更新并仅提示安装,感谢 API Play 提供的新功能)。

正如有人指出的那样,您的代码还可以检查安装是否来自 Play (packageManager.getInstallerPackageName(getPackageName()).equals("com.android.vending");),但该信息很容易被欺骗,因此不确定它是否增加了安全性。

希望对您有所帮助,

正如@Pierre 已经指出的那样,您执行检查的方式是有效的,但我想提醒您,证书固定可以在 运行 时间被绕过,使用 运行 这样的检测框架 Frida or xPosed, but is still advisable and encouraged to use it as one more security layer. For more insights on this please give a quick read to this article 了解固定如何易于实施,从操作的角度来看它如何成为一场噩梦以及如何绕过它。

Is there a better way to verify an apk?

是的,它存在并且称为移动应用证明。该解决方案将包含一个集成在移动应用程序中的 SDK,可在后台与云服务通信,因此不会影响用户体验。

移动应用证明解决方案保证在 运行 时应用不会受到中间人攻击、被篡改,不会 运行 在已获得 root 权限或越狱的设备中,未连接到调试器,未在模拟器上 运行ning,与上传到应用商店或 google Play 商店的原始版本相同。

因此,成功证明移动应用程序完整性的云服务会发出一个非常短暂的 JWT token,它使用只有 API 服务器和移动应用程序证明服务知道的秘密签名运行宁在云端。在证明失败时,JWT 使用 API 服务器不知道的秘密进行签名。在对 API 服务器的每个请求中,移动应用程序将发送此 JWT 令牌,并且 API 服务器将验证签名有效且令牌未过期,并在失败时拒绝请求验证。

一旦移动应用程序不知道云证明服务使用的秘密,就不可能对 JWT 令牌进行逆向工程,即使移动应用程序被篡改,运行在 root 设备中也是如此或通过成为中间人攻击目标的连接进行通信。

您可以在 Approov(我在这里工作)中找到这样的服务,它具有适用于多个平台的 SDK,包括 Android。集成还需要在 API 服务器代码中进行少量检查以验证 JWT 令牌。

Frida

Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.

xPosed

Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.

JWT Token

Token Based Authentication

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.