Search code examples
androidgoogle-cloud-platformgoogle-apigoogle-oauth

Google Cloud Translate API Key Doesn't Work When Using Android Application Restrictions


I'm trying to implement Google Cloud's Translation API using only a key as authentication in my Android app. I don't have a server or anything to improve security, so I wanted to restrict the API key to only be used by my Android app by specifying the app package name and SHA-1 hash. Here are the settings I used in the Cloud API & Services page:

The API Key Settings Used

To ensure the information was correct, I have the Gradle App ID set to the specified package name:

App Module Gradle ID

I ran the provided keytool command in windows for the SHA-1 hash:

Keytool Command Results for SHA-1 Hash

Gradle's signing report tool also returns the same hash:

Gradle SigningReport Output

When I try to call a simple GET request for supported languages, the response is always a 403 Forbidden error. However, when I remove the restriction of only Android apps and the package/hash setting, the key works. Am I clearly doing something wrong here or forgetting something? When I even log BuildConfig.getPackage(), it returns the same package name. Both ways for getting the SHA-1 hash returned the same hash. I'm not sure what is going wrong.


Solution

  • Finally figured it out a couple days ago. Just sending the API request from the app package does not mean it's encoded with that information somewhere for the API endpoint to know it's from an authorized accessor.

    The requests need two header properties to specify the source package and signature, in the form of:

    {
        "X-Android-Package": "package.name.here",
        "X-Android-Cert": "debug:or:release:signature:here"
    }
    

    This is nowhere in Google documentation. I have no idea why, and it's frustrating that this is apparently something everyone needs to know when using pure REST instead of Google's client libraries.

    One thing this means is that it's not a good idea to hard code these values in, since someone could decompile the apk and get the authorized acccessor credentials for these headers. But you can make it more difficult by using several available functions to get this information.

    Getting the package name is simple:

    context.getPackageName()
    

    Signature requires some work

    public String getSignature(Context context) {
        PackageInfo info = null;
        try {
            info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
            Signature[] sigHistory = info.signingInfo.getSigningCertificateHistory();
            byte[] signature = sigHistory[0].toByteArray();
            MessageDigest md = MessageDigest.getInstance("SHA1");
            byte[] digest = md.digest(signature);
            StringBuilder sha1Builder = new StringBuilder();
            for (byte b : digest) sha1Builder.append(String.format("%02x", b));
            return sha1Builder.toString();
        } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
    

    Make sure to read the SigningInfo documentation to use it correctly.

    From there, set the request headers. I have been using Apache's Http Clients:

    HttpGet getReq = new HttpGet(uri);
    getReq.setHeader("X-Android-Package", context.getPackageName());
    getReq.addHeader("X-Android-Cert", getSignature(context));