Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restricting usage for an Android key for a Google API

My question is about how to properly set the package name and SHA-1 certificate fingerprint in the Google Developers Console in order to restrict usage of my Android API key to my app.

When I don't have anything set in the "Restrict usage to your Android apps" section, my requests to the Google Translate API work properly. The API responds normally with status code 200 and my expected result.

But when I specify a package name and SHA-1 certificate fingerprint for my app using the Developers Console, I consistently get 403 Forbidden responses like the following:

HTTP/1.1 403 Forbidden Vary: Origin Vary: X-Origin Content-Type: application/json; charset=UTF-8 Date: Sun, 29 Nov 2015 21:01:39 GMT Expires: Sun, 29 Nov 2015 21:01:39 GMT Cache-Control: private, max-age=0 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Server: GSE Alternate-Protocol: 443:quic,p=1 Alt-Svc: quic=":443"; ma=604800; v="30,29,28,27,26,25" Content-Length: 729  {  "error": {   "errors": [    {     "domain": "usageLimits",     "reason": "ipRefererBlocked",     "message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed.",     "extendedHelp": "https://console.developers.google.com"    }   ],   "code": 403,   "message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed."  } } 

The request looks like the following. Notice that there's no referer header in the request:

GET https://www.googleapis.com/language/translate/v2?key=XXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXX&source=en&target=es&q=test HTTP/1.1 User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; Nexus 6 Build/LVY48H) Host: www.googleapis.com Connection: Keep-Alive Accept-Encoding: gzip 

I'm assuming that the error message indicates a package name or SHA-1 fingerprint problem, despite its message about a "per-IP or per-Referer restriction". While browser keys allow the setting of a per-referer restriction, I'm using an Android key with nowhere to set a per-IP or per-Referer restriction.

I'm sure I have entered the package name correctly in the Google Developers Console. I'm reading the package name from the package attribute on the manifest tag in my Android manifest file.

I'm also sure I have the SHA-1 fingerprint set correctly in the Google Developers Console. I'm reading this value from my keystore using the command keytool -list -v -keystore /path/to/my/keystore. I get the same value when I read it from the APK file using keytool -list -printcert -jarfile myAppName.apk. I'm installing that same APK file using adb.

Here's what I see in the Developers Console:

console screenshot

I've tested this on multiple devices running stock Android. I get the error response on wifi and on the cell network, whether I'm proxying the traffic or not.

When I remove the restriction from the Developers Console, the app works properly again.

What am I doing wrong here?

Note: Several similar questions have been asked before, but with no adequate answers. I don't want to use a browser key or remove the restriction altogether. I want to get the usage restriction to work properly.

like image 511
rmtheis Avatar asked Nov 29 '15 22:11

rmtheis


People also ask

How do I restrict my API key to specific Android applications?

If you're using Play App Signing instead, you must go to the app signing page on the Play Console to get your certificate fingerprint. Go to the Google Maps Platform > Credentials page. Select the API key that you want to set a restriction on. The API key property page appears.

Should I restrict my Google API key?

For example, if your mobile app only uses the Maps SDK for Android and Places SDK for Android, you can restrict the API key to only those two SDKs. You may set an API key to authorize access to as many APIs and SDKs as you want, but we strongly recommend that you limit the list to only those that are needed.


2 Answers

Everything you've done on Google Developer Console to restrict usage of your api key for Android app is OK. After restricted, this API key will only accept request from your app with package name and SHA-1 certificate fingerprint specified.

So how google know that request's sent FROM YOUR ANDROID APP? You MUST add your app's package name and SHA-1 in the header of each request (obviously). And you don't need GoogleAuthUtil and GET_ACCOUNTS permission.

FIRST, get your app SHA signature (you will need Guava library):

/**  * Gets the SHA1 signature, hex encoded for inclusion with Google Cloud Platform API requests  *  * @param packageName Identifies the APK whose signature should be extracted.  * @return a lowercase, hex-encoded  */ public static String getSignature(@NonNull PackageManager pm, @NonNull String packageName) {     try {         PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);         if (packageInfo == null                 || packageInfo.signatures == null                 || packageInfo.signatures.length == 0                 || packageInfo.signatures[0] == null) {             return null;         }         return signatureDigest(packageInfo.signatures[0]);     } catch (PackageManager.NameNotFoundException e) {         return null;     } }  private static String signatureDigest(Signature sig) {     byte[] signature = sig.toByteArray();     try {         MessageDigest md = MessageDigest.getInstance("SHA1");         byte[] digest = md.digest(signature);         return BaseEncoding.base16().lowerCase().encode(digest);     } catch (NoSuchAlgorithmException e) {         return null;     } } 

Then, add package name and SHA certificate signature to request header:

java.net.URL url = new URL(REQUEST_URL); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); try {     connection.setDoInput(true);     connection.setDoOutput(true);      connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");     connection.setRequestProperty("Accept", "application/json");      // add package name to request header     String packageName = mActivity.getPackageName();     connection.setRequestProperty("X-Android-Package", packageName);     // add SHA certificate to request header     String sig = getSignature(mActivity.getPackageManager(), packageName);     connection.setRequestProperty("X-Android-Cert", sig);     connection.setRequestMethod("POST");      // ADD YOUR REQUEST BODY HERE     // .................... } catch (Exception e) {     e.printStackTrace(); } finally {     connection.disconnect(); } 

Other way, if you are using Google Vision API, you can build your request with VisionRequestInitializer:

try {     HttpTransport httpTransport = AndroidHttp.newCompatibleTransport();     JsonFactory jsonFactory = GsonFactory.getDefaultInstance();      VisionRequestInitializer requestInitializer =     new VisionRequestInitializer(CLOUD_VISION_API_KEY) {     /**          * We override this so we can inject important identifying fields into the HTTP          * headers. This enables use of a restricted cloud platform API key.          */         @Override         protected void initializeVisionRequest(VisionRequest<?> visionRequest)             throws IOException {             super.initializeVisionRequest(visionRequest);              String packageName = mActivity.getPackageName();             visionRequest.getRequestHeaders().set("X-Android-Package", packageName);              String sig = getSignature(mActivity.getPackageManager(), packageName);             visionRequest.getRequestHeaders().set("X-Android-Cert", sig);         }     };      Vision.Builder builder = new Vision.Builder(httpTransport, jsonFactory, null);     builder.setVisionRequestInitializer(requestInitializer);      Vision vision = builder.build();      BatchAnnotateImagesRequest batchAnnotateImagesRequest =     new BatchAnnotateImagesRequest();     batchAnnotateImagesRequest.setRequests(new ArrayList<AnnotateImageRequest>() {{     AnnotateImageRequest annotateImageRequest = new AnnotateImageRequest();      // Add the image     Image base64EncodedImage = new Image();     // Convert the bitmap to a JPEG     // Just in case it's a format that Android understands but Cloud Vision     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();     requestImage.compress(Bitmap.CompressFormat.JPEG, IMAGE_JPEG_QUALITY, byteArrayOutputStream);     byte[] imageBytes = byteArrayOutputStream.toByteArray();      // Base64 encode the JPEG     base64EncodedImage.encodeContent(imageBytes);     annotateImageRequest.setImage(base64EncodedImage);      // add the features we want     annotateImageRequest.setFeatures(new ArrayList<Feature>() {{     Feature labelDetection = new Feature();     labelDetection.setType(TYPE_TEXT_DETECTION);     add(labelDetection);     }});      // Add the list of one thing to the request     add(annotateImageRequest);     }});      Vision.Images.Annotate annotateRequest =     vision.images().annotate(batchAnnotateImagesRequest);     // Due to a bug: requests to Vision API containing large images fail when GZipped.     annotateRequest.setDisableGZipContent(true);     Log.d("TAG_SERVER", "created Cloud Vision request object, sending request");      BatchAnnotateImagesResponse response = annotateRequest.execute();         return convertResponseToString(response);     } catch (GoogleJsonResponseException e) {         Log.d("TAG_SERVER", "failed to make API request because " + e.getContent());     } catch (IOException e) {         Log.d("TAG_SERVER", "failed to make API request because of other IOException " +         e.getMessage()); } 

Add following dependencies to your gradle:

compile 'com.google.apis:google-api-services-vision:v1-rev2-1.21.0' compile 'com.google.api-client:google-api-client-android:1.20.0' exclude module: 'httpclient' compile 'com.google.http-client:google-http-client-gson:1.20.0' exclude module: 'httpclient' 

Hope this help :)

like image 139
Duy Pham Avatar answered Oct 02 '22 18:10

Duy Pham


When using a Google REST-only API, such as Translate, you'll need to use GoogleAuthUtil, which will generate a token for a specific user and package/fingerprint. However, that requires GET_ACCOUNTS permission, which smart users are wary of.

You could also use the AccountManager's getAuthToken() method, but that would require not only the GET_ACCOUNTS permission, but also USE_CREDENTIALS.

You might be best off using an API key and obscuring it a bit.

like image 35
323go Avatar answered Oct 02 '22 20:10

323go