12

I'm trying to implement a way to communicate with my backend-server and be sure that my backend only answers, if it's my application which is calling.

So my idea is, that i just send the SHA1/MD5 fingerprint with the HTTPS POST request and verify it on the backend server. If the fingerprint matches, the server will answer.

So my first question is: How do I get these programmatically at runtime? Is it even possible?

The second question is: Can it be that easy? Or do i really have to set up an OAuth-Server (or use the google-api)?...The thing is, that I think that OAuth is a bit overkill for my use case and I don't want to handle the expiration/refresh-token stuff.

Nico
  • 320
  • 1
  • 4
  • 12
  • "How do I get these programmatically at runtime?" -- you have not said what you want to get the SHA1/MD5 value of. "Can it be that easy?" -- probably not. If this is a fixed value, anyone else can just hardcode that same value. If this is some sort of challenge-response, anyone else can implement the same algorithm with the same data. Your APK is readable by anyone who wants to. – CommonsWare Sep 25 '17 at 17:06
  • SHA1 fingerprint would probably be enough - See my comment @GabeSechan answer – Nico Sep 25 '17 at 17:26
  • @CommonsWare i have a requirement where i need to give away my api to be used in other applications. From security and billing point of view i need to make sure that an authenticated app is access the API. I was wondering, how Facebook and Google use the SHA-1 we provide to them during app creation. Can you please share something in regard of achieving this? – Calvin Mar 28 '19 at 06:48
  • @Calvin: I have no clue, sorry. – CommonsWare Mar 28 '19 at 10:30

4 Answers4

19

I have complemented the solution, proposed by Zulqumain Jutt, to be able to get the the result in the common form, like:

KeyHelper: MD5 56:ff:2f:1f:55:fa:79:3b:2c:ba:c9:7d:e3:b1:d2:af

public class KeyHelper {

    /**
     * @param key string like: SHA1, SHA256, MD5.
     */
    @SuppressLint("PackageManagerGetSignatures") // test purpose
    static void get(Context context, String key) {
        try {
            final PackageInfo info = context.getPackageManager()
                    .getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES);

            for (Signature signature : info.signatures) {
                final MessageDigest md = MessageDigest.getInstance(key);
                md.update(signature.toByteArray());

                final byte[] digest = md.digest();
                final StringBuilder toRet = new StringBuilder();
                for (int i = 0; i < digest.length; i++) {
                    if (i != 0) toRet.append(":");
                    int b = digest[i] & 0xff;
                    String hex = Integer.toHexString(b);
                    if (hex.length() == 1) toRet.append("0");
                    toRet.append(hex);
                }

                Log.e(KeyHelper.class.getSimpleName(), key + " " + toRet.toString());
            }
        } catch (PackageManager.NameNotFoundException e1) {
            Log.e("name not found", e1.toString());
        } catch (NoSuchAlgorithmException e) {
            Log.e("no such an algorithm", e.toString());
        } catch (Exception e) {
            Log.e("exception", e.toString());
        }
    }
}
Artur Dumchev
  • 1,252
  • 14
  • 17
10

You can generate one something like in below example:

private void getKeyHash(String hashStretagy) {
        PackageInfo info;
        try {
            info = getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES);
            for (Signature signature : info.signatures) {
                MessageDigest md;
                md = MessageDigest.getInstance(hashStretagy);
                md.update(signature.toByteArray());
                String something = new String(Base64.encode(md.digest(), 0));
                Log.e("KeyHash  -->>>>>>>>>>>>" , something);

               // Notification.registerGCM(this);
            }
        } catch (PackageManager.NameNotFoundException e1) {
            Log.e("name not found" , e1.toString());
        } catch (NoSuchAlgorithmException e) {
            Log.e("no such an algorithm" , e.toString());
        } catch (Exception e) {
            Log.e("exception" , e.toString());
        }
    }

use Like This:

getKeyHash("SHA");
getKeyHash("MD5");

First Answer: You can use above method it's secure and unique i use it all the time.

Second Answer: You can Use Auth keys but that entirely depends on you , what are you comfortable with

Zulqurnain Jutt
  • 298
  • 3
  • 20
  • I haven't tried it yet, but i guess the problem is, as mentioned by `@GabeSechan`, any application can ask for my fingerprints and that is quite bad for my purpose. – Nico Sep 25 '17 at 18:14
  • @Nico Yes, anyone can. That's actually what hash functions are useful for- I can calculate a SHA hash (or md5 hash) of a file and check that it's what you say it should be and be reasonably sure its the same file. It works for verification by being public, but it fails for authentication for the same reason. – Gabe Sechan Sep 25 '17 at 19:32
  • and how can I get the publish keystore hash to compare ? – Web.11 Jan 30 '19 at 23:20
6

Kotlin Version of Artur's Example key string: "SHA1" or "SHA256" or "MD5".

fun getSig(context: Context, key: String) {
            try {
                val info = context.packageManager.getPackageInfo(
                    BuildConfig.APPLICATION_ID,
                    PackageManager.GET_SIGNATURES
                )
                for (signature in info.signatures) {
                    val md = MessageDigest.getInstance(key)
                    md.update(signature.toByteArray())
                    val digest = md.digest()
                    val toRet = StringBuilder()
                    for (i in digest.indices) {
                        if (i != 0) toRet.append(":")
                        val b = digest[i].toInt() and 0xff
                        val hex = Integer.toHexString(b)
                        if (hex.length == 1) toRet.append("0")
                        toRet.append(hex)
                    }
                    val s = toRet.toString()
                    Log.e("sig", s)
                    
                }
            } catch (e1: PackageManager.NameNotFoundException) {
                Log.e("name not found", e1.toString())
            } catch (e: NoSuchAlgorithmException) {
                Log.e("no such an algorithm", e.toString())
            } catch (e: Exception) {
                Log.e("exception", e.toString())
            }
}
Shyam Sunder
  • 523
  • 4
  • 13
Gopal Singh Sirvi
  • 4,539
  • 5
  • 33
  • 55
5

What you're trying to do is impossible. Anything you send to the server as an id can be copied by another application. That's why you have user's with passwords that aren't in the application- the password from an outside source is the only way to be sure the request is valid. And that only proves the user is valid, not that its from your application.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • But if you use google-oauth api, you can specify which applications, defined by the sha1 fingerprint, are allowed to use the Oauth client id. In my opinion it shows, that it has some security value (I'm not talking about bruteforcing these values) and that it has to be possible to get these values, at least the SHA1 fingerprint at runtime. – Nico Sep 25 '17 at 17:26
  • 5
    Mathematically there is no way to prove identity of an outsider agent based only on the information an outside agent gives you. Anyone else can just lie about what their sha fingerprint is by taking the sha fingerprint of your app and using that. – Gabe Sechan Sep 25 '17 at 17:29
  • 1
    The correct thing to do is authenticate your user, and provide a set of APIs that only allows them to take actions you want them to be able to take (with proper authorization checks in each). Then you don't need to care if they're using your app or not. If they really want to go all out and write their own client, who cares? – Gabe Sechan Sep 25 '17 at 17:32
  • Well, so i guess your answer helps me the most. So for now i will go with "normal" user registration. So at least I can watch for abnormal user request behavior in an elementary way. I think I have to change my app-model at some other points ... – Nico Sep 25 '17 at 19:51
  • 1
    @GabeSechan In such similar situation in my workflow i cannot have user type in password. I need the app vendor to access my server API however, my server do not have user information at all. I kind of license the app vendor to use my API for their specific 1 app. I am not willing to have something hardcoded in the application. I can may be derive something from android application keystore and have something already on the server to know that the request is from an authentic vendor. – Calvin Mar 28 '19 at 06:53
  • @GabeSechan I don't agree with you. What is described by original author is the API authentication method used by Google Cloud Platform from accessing APIs only from "trusted" clients. Like Google Maps here: https://developers.google.com/maps/documentation/android-sdk/get-api-key#restrict_key Obviously they SHA-1 fingerprint of the app keystore is sent in HTTPS with every request and has to match the SHA-1 registered in the backend for the request to be fullfilled – MatPag Aug 26 '20 at 10:14
  • @MatPag You can disagree all you want, you're just disagreeing with the entire security community. There is no way for an untrusted piece of hardware to send an identifying key found/calculated on that hardware and be secure. Because the untrusted hardware can lie. GCP doesn't care about lieing, if someone manages to get your key, you're compromised. It isn't a secure authentication protocol. – Gabe Sechan Aug 26 '20 at 14:50
  • 2
    @GabeSechan To grab the SHA-1 Fingerprint and register it on the backend side, you need the keystore file which you use to sign the app, the keystore password and the combination of alias/password. If an attacker manage to steal your keystore you have a bigger problem probably, it's like stealing the private certificate from a server. I don't say this is the definitive authentication protocol, but in my opinion it is pretty safe and that's why GCP uses it. – MatPag Aug 27 '20 at 10:06
  • @MatPag All you need to do is hook the http libraries on a device with a real copy of the app, find the magic number, and alter your app to use that. Secret numbers sent from a non trusted device are never secure. And if you bring the http libraries into the app, you can use standard reverse engineering techniques instead. This works only if your app isn't interesting enough to draw the attention of someone who knows anything. – Gabe Sechan Aug 27 '20 at 13:10
  • 2
    @GabeSechan If this is easy as you say, why around is not plenty of reports talking about API quota leaks of big apps using GCP? Nobody would use GCP if stealing API quota would be so easy and if this is the case, why Google choose this authentication method? – MatPag Aug 27 '20 at 14:35
  • @MatPag Why do you think there aren't? Its not trivial, but its not difficult either. It happens all the time. – Gabe Sechan Aug 27 '20 at 19:42