14

How this question is not a duplicate?

While @MrUpsidedown offers a few related questions in the comments section, none of them truly answer the part I tried to emphasize here: how to provide API keys securely?

After reading all the answers in the linked questions, I am inclined to write an answer myself.

Context

I am a very fresh Flutter developer who tries to integrate Google maps into a demo application.

To save myself time, I decided to try the most popular Widget library for Google Maps which I was able to find, namely google_maps_flutter. The library's terse documentation shows Android and iOS usage examples and both illustrate the API Keys are provided inline, as a part of the code base:

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("YOUR KEY HERE") // <------------------------------------  O--пп
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>  <--------------------------------------  O--пп

Most developers know that the API keys must be stored securely. The code from the library documentation does not suite production application. The Google Maps Platform documentation explicitly warns against hard-coding API keys:

Do not embed API keys or signing secrets directly in code.

...

Do not store API keys or signing secrets in files inside your application's source tree.

Problem

How do I use this package in a correct, secure way? Is it even possible?

I hear a recurring idea a lot: "The client application can not be ever trusted storing API keys (and similar secrets/credentials) as it can be controlled, manipulated, or reverse engineered by a malicious user." If this is the case, does it mean that I must consider google_maps_flutter package inherently insecure as long as it requires providing the Google Maps API key explicitly?

I am also aware of the API key restriction. I will definitely use it, but it seems to me that it only reduces the "blast radius" if the API keys are compromised. I don't see, however, how that can prevent the API key leak and misuse by a third party.

P.S.

Initially, I wanted to pose my question more broadly: Is there a way to securely deliver, store, and consume the secrets on mobile platforms (Android and iOS)?

Igor Soloydenko
  • 11,067
  • 11
  • 47
  • 90
  • Possible duplicate of [How to hide API Keys in AndroidManifest.xml](https://stackoverflow.com/questions/59473527/how-to-hide-api-keys-in-androidmanifest-xml) – MrUpsidown Feb 02 '21 at 10:32
  • Possible duplicate of https://stackoverflow.com/questions/57575973/hide-google-maps-api-key-from-source-control-in-a-flutter-app – MrUpsidown Feb 02 '21 at 10:33
  • Possible duplicate of https://stackoverflow.com/questions/56415252/restrict-google-directions-api-key-in-flutter – MrUpsidown Feb 02 '21 at 10:33
  • https://medium.com/@fakhiradevina/integrate-your-flutter-app-with-google-maps-getting-the-api-key-hiding-it-from-source-code-tdd-e3672367cc84 – MrUpsidown Feb 02 '21 at 10:35
  • 1
    @MrUpsidown all the linked questions are relevant and helpful. Non of them, however, are saying the truth explicitly: It is impossible to provide the API keys to the Flutter App securely. The logical consequence of this fact is this -- it is important to secure the API keys themselves so that they are of no use to a malicious third party. – Igor Soloydenko Feb 02 '21 at 19:16
  • 1
    @MrUpsidown the Medium article does not offer any clues to dealing with the keys whatsoever. Keeping the keys out of the source code is the least of the issues in this problem. – Igor Soloydenko Feb 02 '21 at 19:21
  • I don't know the answer but I know it's been addressed. If you think you now have the right/better answer you can answer your question and/or one of the linked questions. – MrUpsidown Feb 02 '21 at 19:30
  • I still believe my question differs from the linked ones, even though they are related. I also believe that one should both understand the *answer* AND see the thin but important difference between my and other questions to vote for closing. – Igor Soloydenko Feb 02 '21 at 19:35
  • Fine, that's what I meant... if you think you should answer your question because it's not a duplicate then go ahead, np. – MrUpsidown Feb 02 '21 at 19:37
  • The points you mentioned from the docs about not having the API keys in your code / code source are more like basic advice and security measures that you should take to avoid sharing your API keys with someone or publicly by inadvertence. What's important for sure is that you restrict the key as mentioned in the docs. – MrUpsidown Feb 02 '21 at 19:39
  • No answer mentions that it is virtually impossible (as far as I understand) to hide those keys in a Flutter app. So, yes, I sure will write an answer. – Igor Soloydenko Feb 02 '21 at 20:50
  • @IgorSoloydenko If you find the solution to this major issue, please do provide us an answer. It baffles me that there is simply no way to secure these API keys. I've read that it is best to create a proxy server, where the proxy server stores your API key and communicates with the google maps api, returning all relevant data. However, how can we use this google maps api data in Flutter when there doesn't seem to be a Widget/Plugin to display maps via a parameter. – Cherryholme Feb 16 '21 at 22:19
  • @Cherryholme I see you responded to the same thread in the GitHub I did some time ago. I think, you're on the right path. The API keys on the client device is a ridiculous proposition. The widget API surface should change to make the proxy-server-based architecture possible. Not sure if it will ever happen. P.S. I wrote an extended response below for other devs. I am glad the question did not get "closed as a duplicate" because it's not. – Igor Soloydenko Feb 17 '21 at 19:19

1 Answers1

19

Below are the conclusions of my research.

Client device is an UNTRUSTED environment

The API keys can not be safe on the client device. From the security standpoint, the client device should be treated as compromised.

Therefore, each of the following approaches is UNSECURE:

  • Storing the API keys directly in the App code. The user can decompile the application, run it under debugger, etc., and read the API keys.

  • Storing the API keys indirectly in the App. Providing the API keys via environment variables is no different really. The keys get imprinted in the built application's artifacts and are just are readable as in the previous approach.

  • Delivering the API keys to the App from a controlled service. The user can mess with the app the way tampering results in intercepting key on reception from the remote server. In other words, the classic man-in-the-middle attack.

In the ideal world, API keys never leave your server

Yes, you heard it right. Your API keys should live in the service only you can access. That service should work as a proxy for all the upstream API calls your app is making. You don't expose the keys to the client device, thus they can not be stolen from the device's environment.

However, a new question arises immediately: how do you limit the access to your API endpoint? Well, this is an open question, and is out of scope of this answer. I want to note that if you add an own API key for your service, it is in a way leading you to the chicken or the egg problem. Still, own API key is a step forward because you could throttle client requests coming from specific clients. In other words, you ensure that you're in control of things and you don't exceed the quota for the upstream service such as Google Maps API.

Yet, I don't know how you can reliably identify that the caller is your app (if you ever need that). If I understand correctly, the app id is not reliable due to the previous section — client device can not be trusted.

On google_maps_flutter

The google_maps_flutter library only demonstrates in its documents provisioning of the API Keys by means of hard-coding them in the App (see appendix 1). That is insecure.

As somebody noticed in the flutter's GitHub issues section, nowhere the google_maps_flutter recommends hard-coding the API keys in the app for production environments. However, it fails to illustrate or suggest where else can the API keys be provided from.

More importantly, it does not show how to feed the response/data from your proxy server into the widget itself and this is a huge problem.

I was recommended to restrict the API keys with Google Maps API service itself. Here is what these restrictions look for Android and iOS:

Android apps

Add your package name and SHA-1 signing-certificate fingerprint to restrict usage to your Android app.

iOS apps

Accept requests from the iOS app with the bundle identifier that you supply.

I fail to see how it can possibly work as a reliable instrument. Can't the SHA-1 keys and the bundle identifier be tampered on client's phone?! (I believe, they can.)

The answer to original question

  1. It looks like todays consumers of the library have to use the unreliable key leak mitigation techniques. Either most of the Flutter developers don't care to make enough noise to get the issue fixed, or don't understand the issue in the first place, or just gave up on the idea of getting it to work right.
  2. Currently, it does not seem to be possible to use google_maps_flutter in a secure way, that will avoid the API key compromise.
  3. Things will change if the library provides developers with a way to feed the Google Map's response/data directly into the widget, but I didn't see any information which would suggest it's a planned change for the observable future.

Appendix 1

Android

Specify your API key in the application manifest android/app/src/main/AndroidManifest.xml:

<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>

iOS

Specify your API key in the application delegate ios/Runner/AppDelegate.m:

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:@"YOUR KEY HERE"];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
Igor Soloydenko
  • 11,067
  • 11
  • 47
  • 90
  • 3
    This is a good analysis. I agree with you. It seems most Flutter dev's don't realise the security implications of the examples they copy/read. – Oliver Funk Apr 10 '21 at 07:04
  • @Igor, Have you found a solution to securing Google Map API secret outside of the client since sharing your findings. I haven't found a solution to this issue in my search. – Thomas Jan 24 '23 at 16:04
  • @Thomas I did not unfortunately. I was even thinking about forking the library and make it talk to a proxy service under my control. The proxy would hold the secret(s) such as the API keys and make the actual calls to Google. – Igor Soloydenko Jan 26 '23 at 19:14
  • @Igor, same here. Have you looked at: https://github.com/googlecodelabs/google-maps-web-services-proxy – Thomas Jan 26 '23 at 19:50
  • @Thomas haven't seen this before. Could be a good start. There are many ways to implement proxies, this is probably one of them – Igor Soloydenko Jan 26 '23 at 21:23
  • @Igor, that link uses Google App Engine. I would prefer to use a solution without GAE dependency. – Thomas Jan 26 '23 at 21:40
  • @Thomas I feel you (I'm not a fan of Go or GAE), but this conversation is out-of-scope of this StackOverflow question... – Igor Soloydenko Jan 26 '23 at 21:53