I'm trying check my app's release signature at runtime to prevent tampering. However, what I've been reading is that I need to log the signature during a run so that I can hard code it into my app for runtime comparison. But I see two problems with this:
1) The signature obtained while debugging (reading the log) will be different than the release signature.
2) If I get the release signature and then put it into my code for runtime comparison, won't that change the signature for the next release build... hence a chicken and egg problem?
What am I missing? Do static final strings not change the signature? Likewise, if I log release output does the log code not change the signature? What does and doesn't impact the release signature?
If compute the signature over bytes that include the signature itself then yes, you will have chicken and egg problem. But you don't have to: compute the signature over everything but the signature. That's in fact how signatures generated by jarsigner are done.
But what I think you really want, since apk files are already signed, is to check if the signer information matches your own. And that can be done by comparing the package's signer certificate with your own certificate.
You can see an adopted example from Android Security Cookbook that performs such check here.
If I want to change your apk here are the steps:
Open your apk with a an unzip program
Get one or two resources out of it ( ex: class files).
Make changes to it ( de-copmile, change, recompile)
Put it back in the APK.
Sign it
Publish it
I will have some problem At step 5, as I don't have your private key and your signature, I should create my own and sign it with my key. Then I should hope that no one checks the sign before installing the app ( which no one does! ).
So my tampered app has two differences with your app, changed resources AND changed sign certificate.
If you want to check the changed resources, ( signature over bytes) you will have the chicken and egg problem.
The easiest way to stop me is to hard code your app signature and check if it is changed at run time.
The sample code is at https://gist.github.com/scottyab/b849701972d57cf9562e
A simple method for this check:
private boolean validateAppSignature() {
APP_SIGN= "f10e2821bbbea527ea02200352313bc059445190";
PackageInfo packageInfo = this.getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
// SHA256 the signature
String AppSign = DigestUtils.sha256Hex(signature.toByteArray());
return APP_SIGN.equalsIgnoreCase(AppSign);
}
return false;
}
Of course this is not a 100% solution even the obfuscated code can be changed. So I may find a way to by pass this check. But you make tampering harder for me :)
Related
I would like to know what exactly means the hashes from the field PackageSignatures when you obtain it with
adb shell dumpsys package com.myapp
for a given installed app.
I see two hashes:
signatures=PackageSignatures{abcabca [xyzxyzxy]}
The first one (abc) is different in each installation.
The second one (xyz) is fixed for a given apk.
Moreover, I would like to know whether the second one has any relationship with the public signature of the APK. I have several apks with the same public signature, but the second hash is different. Is that normal?
The signatures line on a recent Android version looks as follows:
signatures=PackageSignatures{9122424 version:2, signatures:[bbb2e2d2], past signatures:[]}
To understand this line we just have to look into the AOSP source code: PackageSignatures.java
First part
The first part abcabca comes from System.identityHashCode(this). Hence it generated the Java hashcode fro the current PackageSignatures instance. As this class has only one filed PackageParser.SigningDetails mSigningDetails; the hash code totally depends on the fields of the class SigningDetails:
Signature[] signatures;
int signatureSchemeVersion;
ArraySet publicKeys;
Version part (newer Android versions)
Your used Android version seems to be a bit old, as nowadays the second part is the signatureSchemeVersion - at the moment there are three signature schemes known: V1 (jarsigner), v2 and v3.
Third part (signatures)
The signatures you are interested in is generated by the following code:
Integer.toHexString(mSigningDetails.signatures[i].hashCode())
Where signatures[i] is an instance of the class [android.content.pm.Signature](https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/content/pm/Signature.java
).
The hashCode() is defined in this class to be generated by Arrays.hashCode(mSignature); where mSignature is a byte array that seem to contain the encoded version of the used X.509 signature certificate. Alternatively the raw byte[] can be supplied, hence it is difficult to tell the concrete content.
But from my understanding on your device this part should be directly linked to the signature of the APK file.
What I Have
I have an Android app with some sensitive data. I want to make sure that the client app calls the server only if it has not been tampered with in any way. Basically, I want to check the integrity of the Android app.
What I Have Done
For that, I have implemented a method that can check if the signature is same as the signature I have signed the APK with. Here is a code sample,
fun isApkSignatureBroken(): Boolean {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, GET_SIGNATURES)
val signatures = packageInfo.signatures
if (signatures == null || signatures.isEmpty()) {
return true
}
return signatures
.map { it.toByteArray() }
.map { hash.getSha1(it) }
.none { it.equals(getRealAppSignature(), true) }
}
Here, getRealAppSignature() returns my actual signature with which I have signed the app.
Observations
I have seen this method to work well sometimes. Whenever a tampered app is discovered, this method returns true and the user is blocked from using the app and I am notified by an event. I have found many users being blocked in this way.
I have seen many other cases being reported in forums and other social media sites, that people are using my app even if they are having tampered apps. I have observed many of my apps having malformed version names and application names being used by many users. Like, if my original apps version is 2.2 then they will create a malformed version 2.2-TERMINATOR.
Analysis
I did some analysis on the apps and found some tampered apps from some forums. Those are clearly modded apps having malformed version and application names. Some even have minor UI changes. I tried to install those apps, but they can't be installed with the message "Package is corrupted".
I tried to run keytool on those apps to check their signature, like this,
keytool -list -printcert -jarfile TAMPERED_APP.apk
but I always got,
keytool error: java.lang.SecurityException: SHA1 digest error for classes.dex
I finally rooted my device, installed Xposed Framework and disabled "App Signature Verification" and then was able to install those apps. What I found was that the app was not getting blocked as my signature checking method always returned "false". That means, it always found the original signature inside the apk.
More Analysis
I spend some more time and was able to unpackage the APK using ApkTool. Inside the META-INF folder I found the CERT.RSA to only contain my original signature and no other signature. I opened the MANIFEST.MF file and found that all the entries have different SHA1 than my original APKs manifest file.
It clearly means that the app was modified and was signed by another signature (hence the change in MANIFEST.MF) but my CERTIFICATE.RSA only had my original signature because of which my app always got the original SHA from PackageManager.
Questions
How is the app being re-signed but the new signature is not stored in CERTIFICATE.RSA?
Why is my original certificate still present in CERTIFICATE.RSA?
If the app was resigned, then multiple signatures should be present? But that is true here.
In this situation, how do I detect that the app has been tampered with? Is there any way I can compute the SHA of the app myself instead of querying the PackageManager?
EDIT #1: The entire code is obfuscated using DexGuard. So, the chance of the tamper detection logic getting messed up is pretty less.
I have made two basic (hello world) apps in android studio with different project names and hence different package names as well .
say App A in package com.example.first.appA
and
App B in com.example.second.appB
Still I am getting same signature keys for my above two apps and I am completely clueless on what could be the reason for same key.
Somebody please help!
Here is the code snippet I am using to get the signatures from my apps .
PackageInfo packageInfo = this.getPackageManager()
.getPackageInfo(this.getPackageName(),
PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
byte[] signatureBytes = signature.toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
final String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
Log.d("Signature", "Here is the value for SIGNATURE:" + currentSignature);
So when I put this code snippet one by one in both of my apps A and B .
I get same value of currentSignature say 478yYkKAQF+KST8y4ATKvHkYibo= in my Log
Note : I have not done any release/ debug mode signing explicitly for my app ,i.e. they should be something by default . (Using android studio)
EDIT After the answers from SO and googling I got the answer that any number of projects you have on your android studio would have same debug key (unless explicitly modified) and you can get different keys for different projects by signing your app.
By default, all debug variants of the apps have same signature (i.e. the same default certificate is used for signing).
If you want your apps to have different signatures, you'll need to generate a new debug certificate and manually add it to one of the apps.
Related links:
How to generate a new keystore.
How to change debug keystore.
The default debug key is auto-generated by Android Studio and stored on ~/.android/debug.keystore (for Linux/Mac systems), and C:\User\YourUser\.android\debug.keystore for Windows systems.
And this same key will be used on every project on that machine unless you delete the key, which then Android Studio will re-auto-generate a new one the next time it runs.
That is absolutely normal behavior and I honestly don't understand why are you worried with it.
If you need to (for whatever reason) to have different signature keys on debug builds, see this: Android Studio 0.4.+ custom debug keystore
I've created an android application, and also a paid key-application that can open some features in the regular application.
I tried to use this code to check if the paid key is installed:
protected static boolean isProInstalled(Context context) {
PackageManager manager = context.getPackageManager();
if (manager.checkSignatures(context.getPackageName(), "com.myapps.appkey")
== PackageManager.SIGNATURE_MATCH) {
//Pro key installed, and signatures match
return true;
}
return false;
}
It worked on my phone when I installed the two APKs that I exported from Eclipse (I think I exported them. Maybe it was directly with "run"/"debug". I can't remember). But when I uploaded them to Google Play - I got a message from a user that said that he bought the key but the features are still blocked.
Is there something I do wrong? What are those signatures anyway? Does it have anything with the alias and keystore when I export the APKs?
The debug key is the default used when you run from the IDE, so while testing, they both were using the same key. But you signed them with different keys for the store, so the signatures won't match.
As #NobuGames mentioned in the comments, at this point since you already published both apps, you can update the free one to check for the key hash of the paid app using a hard-coded string. This theoretically might make it easier for someone to make a premium-unlocked pirate version of your app, although if they are digging into your source code that far, I suspect they would have succeeded anyway. Think of that as a good problem to have (popular enough app for pirates to spend that much time hacking on yours).
This question already has answers here:
How to handle a lost KeyStore password in Android?
(42 answers)
Closed 4 years ago.
I want to update my Application to playstore and I lost my keystore password.Now I am trying to create a new one . Will there be any problem if I build APK with another keystore password ?
As #CRUSADER said, Android Keystore Password Recovery might work for you. I could retrieve my password with it. Here is how to do that. I was on Windows 7.
Run the following command,
java -jar AndroidKeystoreBrute_v1.05.jar
Then you will get the following guide,
AndroidKeystorePasswordRecoveryTool by M#xiking
Version 1.03
There are 3 Methods to recover the key for your Keystore:
1: simply bruteforce - good luck
2: dictionary attack - your password has to be in the dictionary
3: smart dictionary attack - you specify a dictionary with regular pieces you use in your passwords. Numbers are automat
icly added and first letter will tested uppercase and lowercase
args:
-m <1..3> Method
-k <path> path to your keystore
-d <path> dictionary (for method 2 and 3)
-w saves the certificate in a new Keystore with same passwort than key
-start <String> sets start String of the word (for method 1)
-p use common replacements like '#' for 'a'(for method 3) WARNING - very slow!!
-h prints this helpscreen
Max memory: 247M
I've used method 2 because I had some guess of my password. I typed the following command,
java -jar AndroidKeystoreBrute_v1.05.jar -m 2 -k android.keystore -d dict.txt
In case you might wonder, my dict.txt was something like this,
gussedpassword1
gussedpassword2
gussedpassword3
I could successfully retrieve my password and alias.
Well, you are not the first to lose keystore password..
I use this one for bruteforce when I got stuck: Android Keystore recover
You can go with few guesses.
And as #StinePike mentioned.. you cannot continue updating your existing applications
There will be no problem for uploading new application. But you can not continue updating your existing application with new keystore. You need to create new applications by resubmitting them if you want to update your previous applications.
Always preserve the keystore with a best possible way :(
If you have lost the password to the keystore, then the only option is to create a new signing key. There will be no problem doing so.
However, you will not be able to update the existing application. And neither can you upload another application with the same package name.
For example, if you current package is:
com.example.mycurrentapp
You will need to use a different package name to go along with your new signing key. For example, you can change the package name to:
com.example.mynewapp
The above, are of course, illustrations. ;-)
The Google Play ecosystem identifies different applications using their package names and therefore, they have to be unique. Unfortunately, if you have a good user base, unless they install your new app, they will not be able to get updates to the older app.
It's unfortunate, but when you lose your keystore, or the password to your keystore, your application is orphaned. The only thing you can do is resubmit your app to the market under a new key. You will not be able to update your app without the key.
You will have to publish your app again with a new keystore and under a different package name, or remember your password.