codeux.design

Flutter App Developer and UX Designer


Home Blog Contact
Teaser Manage secrets in a flutter project with Blackbox

Manage secrets in a flutter project with Blackbox

22 October 2019

For every flutter mobile app there are certain secrets which have to be integrated into the app. These might include API Keys or private keys for services like Firebase, Analytics, etc. Or provisioning profiles for iOS.

This is the first part of what will become three parts on how to build flutter apps on a CI.

  1. Setting up Blackbox & Managing Signing Keys (This article)
  2. Using Blackbox on a CI (Coming soon)
  3. Building release builds on Travis (Android) and Cirrus CI. (iOS) (Coming soon)

Outline

In this article you will get a quick overview on how to manage secrets you usually encounter in flutter apps:

  1. First a quick explanation of Blackbox, a tool we will use to encrypt all secrets.
  2. Signing keys for android and how to configure gradle.
  3. Provisioning profiles for iOS with fastlane match.

Blackbox Overview

It is generally not a good idea to add plaintext API keys, passwords, etc. to your git repository. But it is very convenient. This is where Blackbox comes in and allows you to store encrypted secrets along side your flutter app.

Possible secrets could include:

  • Upload or signing keys.
  • Secret API Keys for services used in the app (like Google Analytics, etc.).
  • Provisioning Profiles or similar.

Installation & Set Up

Blackbox are a collection of shell scripts based on GPG to store secrets inside public git repositories.

Installation is as easy as cloning the git repository and adding it’s bin/ directory to your $PATH. I would recommend cloning my fork because I’ve added a useful blackbox_create_role_account script.

git clone --branch create_role_account https://github.com/hpoul/blackbox/
export PATH=$(pwd)/blackbox/bin

Afterwards to get started you have to set up and create a GPG signing key.

Now change into your git repository and we’ll add blackbox. Make sure to exchange my@email.com with your actual email address which includes your GPG key.

bash$ blackbox_initialize
> Enable blackbox for this git repo? (yes/no) yes
>  VCS_TYPE: git

bash$ blackbox_addadmin my@email.com
gpg: keybox '/myrepo/.blackbox/pubring.kbx' created
gpg: /myrepo/.blackbox/trustdb.gpg: trustdb created
gpg: key 3F58C943BC3603F4: public key "Max Mustermann <my@email.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1


NEXT STEP: You need to manually check these in:
      git commit -m'NEW ADMIN: herbert@poul.at' .blackbox/pubring.kbx .blackbox/trustdb.gpg .blackbox/blackbox-admins.txt

bash$ git add .
bash$ git commit -m 'initialized blackbox'
[master (root-commit) 65384bd] initialized blackbox
 6 files changed, 6 insertions(+)
 create mode 100644 .blackbox/.gitattributes
 create mode 100644 .blackbox/blackbox-admins.txt
 create mode 100644 .blackbox/blackbox-files.txt
 create mode 100644 .blackbox/pubring.kbx
 create mode 100644 .blackbox/trustdb.gpg
 create mode 100644 .gitignore
bash$

Configure signing keys for android

It’s a good practice to keep your secrets in one place instead of scattering them across the whole project. So I tend to have a _tools/secrets folder in my projects which contain all non-public files.

The easiest way to keep your android release and upload keys secure is to add a new build_secrets.gradle.kts file which is conditionally imported into your main gradle file if it is available.

(My examples use gradle kotlin dsl. it should only require minor adjustments for gradle groovy syntax).

_tools/secrets/android_build_secrets.gradle.kts

allprojects {
    // requires path relative to the `android` directory.
    extra["secrets.keyAlias"] = "MyKeyAlias"
    extra["secrets.storeFile"] = "../_tools/secrets/android_upload_key.jks"
    extra["secrets.storePassword"] = "MySecretPassword"
}

Now copy your upload or signing key to _tools/secrets/android_upload_key.jks and add both to blackbox:

bash$ blackbox_register_new_file _tools/secrets/android_build_secrets.gradle.kts _tools/secrets/android_upload_key.jks 
      ========== PLAINFILE _tools/secrets/android_build_secrets.gradle.kts
      ========== ENCRYPTED _tools/secrets/android_build_secrets.gradle.kts.gpg
      ========== PLAINFILE _tools/secrets/android_upload_key.jks
      ========== ENCRYPTED _tools/secrets/android_upload_key.jks.gpg
Local repo updated.  Please push when ready.
    git push
bash$

You should now be left with only .gpg files and have the real files encrypted and added to your .gitignore. To be able to continue using those files, run blackbox_postdeploy

bash$ blackbox_postdeploy
========== EXTRACTED _tools/secrets/android_build_secrets.gradle.kts
========== EXTRACTED _tools/secrets/android_upload_key.jks
========== Decrypting new/changed files: DONE
bash$

android/app/build.gradle.kts

val secretConfig = file("../_tools/secrets/build_secrets.gradle.kts")
if (secretConfig.exists()) {
    apply { from(secretConfig) }
} else {
    println("Warning: Secrets not found, release signing disabled.")
}

// ...

android {
    signingConfigs {
        release {
            keyAlias project.properties["secrets.keyAlias"] ?: ""
            keyPassword project.properties['secrets.storePassword']
            // Provide a dummy default value, null would break builds.
            storeFile file(project.properties['secrets.storeFile'] ?: "invalid")
            storePassword project.properties['secrets.storePassword']
        }
    }
}

🎉️ aaaand now you are done. Feel save to commit and push your changes. Your secrets are save, but you can still conveniently export your signed appbundles/apks.

Secret API Keys for your flutter app

To access “secret” API keys (for example for accessing services like firebase or analytics) you can use a separate entry point for your flutter app.

So instead of using the default lib/main.dart you create your custom lib/env/production.dart with a main method like:

lib/env/production.dart

import '../main.dart';
import './production_secrets.dart';

void main() {
    startApp(ProductionSecrets());
}

Now modify your main.dart so we start our app with the provided secrets. We can then use the Provider package to make it available throughout the app.

lib/main.dart

import 'package:provider/provider.dart';

abstract class Secrets {
    abstract String get firebaseApiKey;
}

void startApp(Secrets secrets) {
    runApp(MyApp(secrets: secrets));
}

class MyApp extends StatelessWidget {
  const MyApp({Key key, this.secrets}) : super(key: key);

  final Secrets secrets;
  @override
  Widget build(BuildContext context) {
    return Provider<Secrets>.value(
      value: secrets,
      child: // ...
    );
  }
}

Now simply add your secrets as an implementation of the Secrets class and you are ready to go:

lib/env/production_secrets.dart

class ProductionSecrets implements Secrets {
  @override String get firebaseApiKey => 'D484fDKJE4';
}
Note

To keep things tidy you can put this file into _tools/secrets/ and symlink it into your lib/env/ folder.

Make sure to register the file with blackbox so it is encrypted correctly.

bash$ blackbox_register_new_file lib/env/production_secrets.dart
========== PLAINFILE lib/env/production_secrets.dart
========== ENCRYPTED lib/env/production_secrets.dart.gpg
========== UPDATING VCS: DONE
Local repo updated.  Please push when ready.
    git push

Update release script to use new secrets.

Now you have to tell flutter build (and flutter run) to use your new secrets file. To do this simply pass in the -t parameter:

bash$ flutter build appbundle -t lib/env/production.dart

If you require it during production you can also set the entrypoint in Android Studio:

Configure Dart Entrypoint to use your secrets.

Configure Dart Entrypoint to use your secrets.

iOS: Manage Provisioning Profiles

Fastlane match is a convenient way to share provisioning profiles with your team or different CI environments.

It uses a remote git repository (or google cloud storage) to securely store provisioning profiles and install them using fastlane match.

My recommendation is to store the fastlane configuration inside your ios project but reference your secrets inside _tools/secrets to keep all secrets in one place.

Follow the setup instructions on fastlane’s website the “secret” configuration basically consists of the following variables:

_tools/secrets/fastlane_match_password

export MATCH_KEYCHAIN_NAME="mykeychainname"
export MATCH_PASSWORD="fastlaneMatchEncryptionPassword"
export FASTLANE_PASSWORD="itunes connect store password"

and don’t forget it to register it with blackbox:

bash$ blackbox_register_new_file _tools/fastlane_match_password
========== CREATED: _tools/fastlane_match_password.gpg
========== UPDATING VCS: DONE
Local repo updated.  Please push when ready.
    git push

SSH Access

For fastlane to access the git repository where you store the provisioning profile you should create a new private key - for example on GitHub or GitLab you can register deploy keys. So if you do not yet have a ssh key you can create a new one using

bash$ ssh-keygen -f _tools/fastlane_match_certificates_id_rsa
  Generating public/private rsa key pair.
  Enter passphrase (empty for no passphrase): 
  Enter same passphrase again: 
  Your identification has been saved in _tools/fastlane_match_certificates_id_rsa.
  Your public key has been saved in _tools/fastlane_match_certificates_id_rsa.pub
bash$ blackbox_register_new_file _tools/fastlane_match_certificates_id_rsa

Using your secrets with fastlane

Now if you are want to actually access the repository to install all provisioning profiles (for example on a CI server) you can simply import the private key, source the password file and launch fastlane match:

cat _tools/secrets/fastlane_match_certificates_id_rsa | ssh-add -
source _tools/secrets/fastlane_match_password
fastlane match

More on how to create a full CI release cycle for your flutter app in a later article 😉️