Security in Flutter: google_maps_flutter requires API Keys but how to provide them securely?

1,026

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
Share:
1,026
Igor Soloydenko
Author by

Igor Soloydenko

24/7: Loving husband, Dog friend, Critic Software Developer C# and TypeScript code reviewer @ CR.SEm

Updated on November 28, 2022

Comments

  • Igor Soloydenko
    Igor Soloydenko over 1 year

    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)?