Detect if App is Production/Release Version from Code - android

I have a home made logging system in my app. Of the things that it does is preform alot of logging (to file and logcat) while in development and can be turned off completely with one varible change. For a functionality example:
static public final boolean DEVELOPMENT_VERBOSE = true;
public static void developmentLogMessage(String message) {
if (DEVELOPMENT_VERBOSE)
Log.i("com.xxx.app", message);
}
The problem (maybe more an annoyance) I have is that I must remember to set DEVELOPMENT_VERBOSE = false for release. Is there a way in code to detect when the app is finalized for release (say checking for a signed apk for example) so I can set DEVELOPMENT_VERBOSE to false programmatically?
I looked at Detect if app was downloaded from Android Market but it seems my app has a signature even before signing it for the market.
try {
PackageManager manager = context.getPackageManager();
PackageInfo appInfo = manager.getPackageInfo(
"com.xxx.app", PackageManager.GET_SIGNATURES
);
System.out.println(appInfo.signatures[0].toCharsString());
} catch (NameNotFoundException e) {
}
I was hoping the signatures array would be empty and I could key of of that. But no go.

You can use ProGuard to completely turn appropriate logs off when building a release. ProGuard can do a lot of interesting stuff. Among other things it can shrink unneeded code during the building process. For example, if you use debug log (Log.d()) during development, but want to disable it in release then you can add these lines to your proguard.cfg:
-assumenosideeffects class android.util.Log {
public static int d(...);
}
To enable ProGuard, set the property
proguard.config=proguard.cfg
to your project.properties (if you use default locations). Be noted that ProGuard will also do some other things by default so you probably should take some additional steps when releasing your project. At least you certainly want to save generated mapping.txt file. See the ProGuard guide for more details.

You can look at which certificate was used to sign the app, and act accordingly.
For example:
for (Signature sig : getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
// Get some (pseudo)uniqe value:
long sigHash = Arrays.hashCode(sig.toByteArray());
if (sigHash == releaseSigHash) DEVELOPMENT_VERBOSE = false;
}
Here is what I do for Google MapView, to decide which API Key to use, which is a similar problem
for (Signature sig : getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(sig.toByteArray());
md5 = new BigInteger(1, m.digest()).toString(16);
Log.d("findApiNøgle", "md5fingerprint: "+md5);
// Jacobs debug-nøgle
if (md5.equals("5fb3a9c4a1ebb853254fa1aebc37a89b")) return "0osb1BfVdrk1u8XJFAcAD0tA5hvcMFVbzInEgNQ";
// Jacobs officielle nøgle
if (md5.equals("d9a7385fd19107698149b7576fcb8b29")) return "0osb1BfVdrk3etct3WjSX-gUUayztcGvB51EMwg";
// indsæt din egen nøgle her:
}

After some research/work we can up with a solution that checks against the singed cert.
static public boolean DEVELOPMENT_VERBOSE = false;
static private final X500Principal RELEASE_DN = new X500Principal(
"CN=aaa,OU=bbb,O=ccc,L=ddd,ST=eee,C=fff"
);
// auto disable the development logs if the apk is signed with a cert
try {
PackageManager manager = context.getPackageManager();
PackageInfo appInfo = manager.getPackageInfo("com.xxx.app",
PackageManager.GET_SIGNATURES);
Signature raw = appInfo.signatures[0];
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
//DEVELOPMENT_VERBOSE = cert.getSubjectX500Principal().equals(DEBUG_DN);
if (!cert.getSubjectX500Principal().equals(RELEASE_DN))
DEVELOPMENT_VERBOSE = true;
} catch (CertificateException e) {
}
} catch (NameNotFoundException e) {
}
As long as you use the same cert from version to version of the app this will always work.

Related

I want to develop a android sdk for our clients to use,how to ensure that only the client who apply for an APP_KEY can use my sdk?

Developers or clients who want to use my skd should go to my website to apply for a APP_KEY.To apply for a APP_KEY,they should offer the app's package name,something like this "com.edward.myapp".Also the SHA-1 of the developers's certificate(generated from keytool).
Now I can only send the APP_KEY to my server to verify the APP_KEY,the only thing I know is that the APP_KEY is valid or invalid.But I don't know if it is my clients' app,it can be anyone else's app,and it can be any package name.I don't want to something like this happen.
So how can I verify it to ensure it is the right app to use the sdk?
Your SDK at runtime can obtain the SHA1 of the certificate used to sign the APK.
String getSignatureSha1(Context context) {
PackageManager pm = context.getPackageManager();
PackageInfo info = null;
try {
info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not find info for " + context.getPackageName());
return null;
}
byte[] signatureBytes = info.signatures[0].toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signatureBytes);
byte[] digestBytes = md.digest();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < digestBytes.length; i++) {
// you can omit this if block if you don't care about the colon separators
if (i > 0) {
builder.append(':');
}
builder.append(String.format("%02X", digestBytes[i] & 0xFF));
}
return builder.toString();
}
You should do this once when the app starts and cache the result if you need it more than once.

Error comparing APK Signature with original Signature

I am trying to compare Signature verification of my APK at run time with the original Signature key "The same one!", I follow that answer so it's mustn't kill my app because it's the same one!, but it's kills the app as it's not the same one and show the toast.
That's the code
public void checkSignature(final Context context) {
try {
signatures = context.getPackageManager()
.getPackageInfo(context.getPackageName(),
PackageManager.GET_SIGNATURES).signatures;
if (signatures[0].toString() != SIGNATURE_KEY) {
// Kill the process without warning. If someone changed the certificate
// is better not to give a hint about why the app stopped working
android.os.Process.killProcess(android.os.Process.myPid());
Toast.makeText(getApplicationContext(), "Not working", Toast.LENGTH_LONG).show();
}
} catch (PackageManager.NameNotFoundException ex) {
// Must never fail, so if it does, means someone played with the apk, so kill the process
android.os.Process.killProcess(android.os.Process.myPid());
}
}
I used that code to get the Signature code at runtime more than time and every time gives me the same! "it's happens when i tap on button"
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Release", signatures[0].toCharsString());
clipboard.setPrimaryClip(clip);
So What's wrong with that code makes the comparing process not working correctly?
You compare strings with using != operator. This compares strings as links, not objects. You should use .equals().
Edited:
Also for properly compare signatures :
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signatures[0].toByteArray());
String signature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
if (!signature.equals(SIGNATURE_KEY)){
//do your logic
}

How to read SHA and MD5 fingerprint programmatically in Android

Hello I want to read SHA and MD5 fingerprint value of keystore programmatically of my app from which it was signed.
I'll take either SHA or MD5 value as key for security. This key I will use in the code to encrypt something and decrypt same at server end.
Is there any way to find this or is there any way to do same using different good approach. This should be in such a way nobody other can find this key.
Thanks in advance.
PackageInfo info;
try {
info = getPackageManager().getPackageInfo(
"com.your.package.name", PackageManager.GET_SIGNATURES);
for (Signature signature : info.signatures) {
MessageDigest md;
md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
String hash_key = new String(Base64.encode(md.digest(), 0));
}
} catch (NameNotFoundException e1) {
} catch (NoSuchAlgorithmException e) {
} catch (Exception e) {
}
try this:
/**
*
* #param pkg packageName
* #return
*/
public String getSingInfo (String pkg) {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
String s = getMd5(sign);
return "md5:" + s ;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private String getMd5 (Signature signature) {
return encryptionMD5(signature.toByteArray());
}
public static String encryptionMD5(byte[] byteStr) {
MessageDigest messageDigest = null;
StringBuffer md5StrBuff = new StringBuffer();
try {
messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update(byteStr);
byte[] byteArray = messageDigest.digest();
for (int i = 0; i < byteArray.length; i++) {
if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
} else {
md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
}
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return md5StrBuff.toString();
}
Find the path of your application's APK file by calling Context.getPackageCodePath()
Copy that APK to a writeable directory of your choice
Use apk-parser to get the information you need from the APK (see example below)
That library is able to decompress the APK file and parse all of its content. An example extracted from the apk-parser's Github page, tailored to your needs:
try {
ApkParser apkParser = new ApkParser(new File(filePath));
ApkSignStatus signStatus = apkParser.verifyApk(); // not needed
List<CertificateMeta> certs = apkParser.getCertificateMetas();
for (CertificateMeta certificateMeta : certs) {
System.out.println(certificateMeta.getCertMd5());
}
} catch (Exception e) {
e.printStackTrace();
}
Added below solution in-case if someone is looking for it for the first time, and don't know how to get it from studio as well.
Many times we search and get suggestion links.
Easiest Way
Open Android Studio
Open Your Project
Click on Gradle (From Right Side Panel, you will see Gradle Bar)
Click on Refresh (Click on Refresh from Gradle Bar, you will see List Gradle scripts of your Project)
Click on Your Project (Your Project Name form List (root))
Click on Tasks
Click on android
Double Click on signingReport (You will get SHA1 and MD5 in Run Bar)
Check Screenshot below:

Obtain Private Key from inside app

Is there any way to get the private key that current APK was signed with?
The operation is completely safe, since any modification (injection) to app would need a new signing and new key pair. So if private key (that developer is aware of that) is accessible to the running code, it's only accessible to the original code not a malicious one.
X509Certificate allows access to PublicKey, but I need access to PrivateKey.
public static X509Certificate GetCertificate(Context context) {
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch(CertificateException e) {
e.printStackTrace();
}
X509Certificate c = null;
try {
c = (X509Certificate) cf.generateCertificate(input);
} catch(CertificateException e) {
}
return c;
}
Edit:
A side question can be:
Is there any way to ensure that currently running code is running inside the APK with the original certificate?
(Everybody have access to public key, so it's not a good candidate to check against, but private key is only known by original developer which can protect it (in some way) and check package certification against that.)
The private key is used only for signing the APK and never (in theory) leaves the place of signing. So it's not possible to recover it from the APK.
Update: and no, the code itself can't check the validity of its own image. If the image has been altered, it's trivial to remove the check as well.

SDK for Android:How facebook check the Hash key?

When we want to use Facebook SDK for Android as our SSO solution, we need to put our Android application signature into our Facebook application settings (Step 5 of Facebook sdk for android).
And that signature should be generated by running the keytool that comes with the Android SDK.
I am curious how facebook verify this signature?
After more than one year, I think I'd better answer my question.
Android's app can get other app's signature by:
public String WriteSignature(String packageName)
{
PackageManager pm = this.getPackageManager();
String sig = "";
PackageInfo pi = null;
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e1) {
e1.printStackTrace();
}
try {
for (Signature signature : pi.signatures) {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
sig = Base64.encodeToString(md.digest(), Base64.DEFAULT);
Log.d(ACTIVITY_TAG, sig);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return sig;
}
For facebook app to function we need to provide Facebook "App Id"
For android platform we need to provide package name, class name & "HashKey
App will mostly have 2 HashKey for "Debug" & "Release" version of app
When app access facebook, SDK internally generate & compare the HashKey with one submitted during the facebook app

Categories

Resources