How to validate the APK signing certificate at client side?

Viewed 2934

I am developing an android app which is talking with a server, and I want to verify at run time that my app has not been modified since I released it(a user with a modified app should not be able to login to app).

Since modified app's signature is different from the original signature, I decided to :

  1. Extract signing certificate/s which is embedded in the android app
  2. send it to the server with the login request (Coding the whole verification process in client side(i.e. apk) does not work as someone could modify the apk to bypass it.)
  3. verify it
  4. if certificate is valid process login request / else return error

This is my code for number 1 above :

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;  

So my questions are :

  1. Is there a better way to verify an apk?
  2. If this method is okay,
    2.1 Would sending a certificate cost high in bandwidth?
    2.2 How can I verify the certificate?(I have mykey.jks at server side which I originally used to sign the apk)

(Also this is my first ever question on stackoverflow, so pointing out any mistakes I did in asking the question are highly appreciated!. )

2 Answers

First of all, it will always be possible for someone to bypass this kind of security, e.g. by hardcoding the certificates passed to your server. You can only try to make it as hard as possible but it will never be 100% secure.

That being said what you're doing seems ok. Note that pm.signatures is deprecated in favour of pm.signingInfo in the latest SDK.

Size wise, a certificate is not big, but to make the requests even smaller, you should probably only send a hash (e.g. SHA256).

To prevent people from hardcoding the certificate in the request, you could also consider hashing another value together with the certificate, i.e. hash(certificate + timestamp), and send also the timestamp in the request so you can recompute the hash server side. And if the timestamp is too far away from the current date, reject the request. Again, it's not perfectly secure, but adds another layer of complexity to reverse engineer your code. You could also add the versionCode and start rejecting requests of old versions (e.g. if you detect a security bug in one of your old versions) and prompt those users to update the app (or do it for them in the background and only prompt for the install, thanks to the new API Play provides).

As someone pointed out, your code could also check out that the install came from Play (packageManager.getInstallerPackageName(getPackageName()).equals("com.android.vending");), but that information is easily spoofable so not sure it adds much security.

Hope that helps,

As already pointed out by @Pierre the way you perform the check is a valid one but I would like to alert for the fact that certificate pinning can be bypassed at run time, with instrumentation frameworks like 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 to understand how pinning is easy to implement, how it can be a nightmare to maintain from the operational point of view and how it can be bypassed.

Is there a better way to verify an apk?

Yes it exists and is called Mobile App Attestation. This solution will consist of a SDK integrated in the mobile app that communicates with a cloud service on the background, thus with no impact on the user experience.

The Mobile App Attestation solution guarantees at run-time that an app is not being man in the middle attacked, being tampered with, not running in a rooted or jail broken device, not attached to a debugger, not running on an emulator and that is the same original one uploaded into the app store or google play store.

So the cloud service on successful attestation of the mobile app integrity issues a very short lived JWT token that is signed with a secret only known by the API server and by the Mobile App Attestation service running in the cloud. On an attestation failure the JWT is signed with a secret not known by the API server. In each request to the API server the mobile app will send this JWT token and the API server will verify that the signature is valid and the token have not expired and will refuse the request when it fails any of the verifications.

Once the secret used by the cloud attestation service is not known by the mobile app it is not possible to reverse engineer the JWT token, even when the mobile app is tampered, running in a rooted device or communicating over a connection that is being the target of a Man in the Middle Attack.

You can find such a service in Approov(I work here), that have SDKs for several platforms, including Android. The integration will also need a small check in the API server code to verify the JWT token.

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.

Related