14

When registering an Android app in the API console for Google API access you have to enter your apps SHA1 certificate fingerprint and the package name of the app.

Now I was wondering how Google could verify this values are correct when the api calls are just simple HTTP requests (in the simplest case, when you don't use their API client, that could append some header values)? You must provide your API key when making a API call, but this doesn't proves that the entered values are correct.

Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
lukstei
  • 844
  • 2
  • 8
  • 20

2 Answers2

10

You can obtain the package name as well as the sha 1 fingerprint of the installed application quite easily.

private void printSha1() {
    List<ApplicationInfo> mAppList = getPackageManager().getInstalledApplications(0);
    for (ApplicationInfo info :mAppList) {
        Log.d(TAG, "Package Name: " + info.packageName);
        Log.d(TAG, "Sha1: " + getCertificateSHA1Fingerprint(info.packageName));
    }
}

private String getCertificateSHA1Fingerprint(String packageName) {
    PackageManager pm = getPackageManager();
    int flags = PackageManager.GET_SIGNATURES;
    PackageInfo packageInfo = null;
    try {
        packageInfo = pm.getPackageInfo(packageName, flags);
    } 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) {
        e.printStackTrace();
    }
    String hexString = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        byte[] publicKey = md.digest(c.getEncoded());
        hexString = byte2HexFormatted(publicKey);
    } catch (NoSuchAlgorithmException e1) {
        e1.printStackTrace();
    } catch (CertificateEncodingException e) {
        e.printStackTrace();
    }
    return hexString;
}

public static String byte2HexFormatted(byte[] arr) {
    StringBuilder str = new StringBuilder(arr.length * 2);
    for (int i = 0; i < arr.length; i++) {
        String h = Integer.toHexString(arr[i]);
        int l = h.length();
        if (l == 1) h = "0" + h;
        if (l > 2) h = h.substring(l - 2, l);
        str.append(h.toUpperCase());
        if (i < (arr.length - 1)) str.append(':');
    }
    return str.toString();
}

If you run this code it will print the package name and its sha 1 print. These are the two things that you provide while creating API key, so Google maps these two things against the key it generates.

As you can see its possible to access package name and its SHA 1 print the other remaining thing is the API key which you provide to the necessary library or other application (Google Play Service) via your code or through xml(Manifest, separate xml config file).

So whenever the Google serves you anything it can check the relevant mapping that it generated when you generated the key from API console.

The code for package name is take from here

Community
  • 1
  • 1
Anirudha Agashe
  • 3,510
  • 2
  • 32
  • 47
2

If you are not using Google Utils/SDKs/API clients, you'll have to pass those headers manually.

If you have restricted your API key for android or ios apps, you'll have to pass following headers-

Android headers:

{
  'x-android-package': 'com.example',
  'x-android-cert': '50FEC39F742F3DF212BDC2131A99C7D3C82086F6'
}

Here value of x-android-cert header is SHA1 fingerprint of signing key without semicolon.

To get SHA1 fingerprint from keystore file-

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

To get SHA1 fingerprint from signed apk/bundle-

keytool -printcert -jarfile ~/Downloads/app-release.aab

keytool -printcert -jarfile ~/Downloads/app-release.apk

ios headers:

{
  'x-ios-bundle-identifier': 'com.example' 
}

Here com.example is bundle identifier.

Below is the class in Java which I think is doing the same job in native Places SDK-

/**
 * Intercepts requests and provides Android-specific headers so that API key restrictions can be
 * enforced.
 */
public class AndroidAuthenticationInterceptor implements Interceptor {

  ...

  @NotNull
  @Override
  public Response intercept(@NotNull Chain chain) throws IOException {
    ...

    final Request.Builder builder = chain.request().newBuilder();
    if (config.packageName != null) {
      builder.addHeader(HttpHeaders.X_ANDROID_PACKAGE, config.packageName);
    }

    if (config.certFingerprint != null) {
      builder.addHeader(HttpHeaders.X_ANDROID_CERT, config.certFingerprint);
    }

    return chain.proceed(builder.build());
  }
}

References-

  1. Restricting usage for an Android key for a Google API
  2. How do I find out which keystore was used to sign an app?
  3. https://developers.google.com/maps/api-security-best-practices
  4. https://github.com/googlemaps/google-maps-services-java/blob/9852dbae5d2c10897ad7a8dd4befcd171a2cd48e/src/main/java/com/google/maps/android/AndroidAuthenticationInterceptor.java
  5. https://github.com/googlemaps/google-maps-services-java/blob/main/src/main/java/com/google/maps/internal/HttpHeaders.java
Varun Kumar
  • 2,543
  • 1
  • 23
  • 22