Mobile Secrets

Handle secrets the secure way with ease

Cyril Cermak
Advanced iOS Engineering

--

In this article, I will show how to use Mobile Secrets gem alongside with GPG to handle app secrets the most secure way. Mobile Secrets is an open-source project for handling secrets for mobile development. Contributions are highly welcomed. 🛠📱👷‍♀️👷‍♂️

The first part of the article is more for POs/PMs, feel free to skip to the more technical part if you are already familiar with the basics.

What is a secret?

A secret is every sensitive information within your app. This piece of information can do real harm to you, your app or business if it is leaked out. The secret is mostly string, but it can also be a certificate, some local file, etc.

Why should secrets be handled differently?

Let us say that you developed an app that is happily living in the AppStore or PlayStore. The app is built on top of Firebase. Therefore, you have to have your Firebase secret key inside the app to initialise the service. You are a good programmer so one of your jobs is making good and meaningful names for variables.

Here it comes, you named it firebaseSecret and assigned your secret key to it as a string. Nothing wrong with that, up until one day…

Your app hit the sky, everyone loves it, it brought too much of an attention. Unfortunately, not only the good one. Obsessed with money, having a black hat on and Kali Linux installed in VM, a hacker will download the app on his jailbroken iPhone. He extracted the IPA out of the phone and reversed engineered the app binary. Out of that, all the in-app strings can be listed.
(Assuming the SSL Pinning was implemented otherwise, it would be even easier with MITM attack.)

Yes you guessed it, it will contain your firebaseSecret. The hacker won’t still have access to the Firebase console, however, it can download the whole database of users, their photos with puppies, etc. and… then wipe it out… and sell it back to you.

Let’s have a look at how it can look like.

Reverse engineered binary

I used IDA to disassemble the compiled binary and here is what I found.

IDA disassembled binary

_$S3App7SecretsV14firebaseSecretSSvau+15 — 3 characters long App, contains 7 characters long Secrets with 14 characters long variable firebaseSecret who has assigned 15 characters long string in the subroutine. The Secrets is a struct within the App that contains static let with the name firebaseSecret who has assigned the secret string.

IDA is way too complex for just finding strings, there is nm — display name list a program that will list all strings within a compiled binary into a console by simply executing nm ./CompiledBinary.

How to handle secrets

First thing first, the secret must be obfuscated, without a doubt. You can read more about code obfuscation here and here.

Unfortunately, obfuscation might not be enough. What if there is a colleague who has access to the source repository or someone who might want to steal these secrets and hand it out? Simply downloading the repo and printing the de-obfuscated string into a console would do it.

In general, the secret should be visible only for the right developer in any circumstances. Especially, for mono-repository projects where many teams are contributing to simultaneously.

In the second part of the article, I will show how to use Mobile Secrets gem and GPG to obfuscate and protect your secrets.

Protecting secrets with GPG

GPG is the foundation of the Mobile Secrets, therefore some basic knowledge is necessary in order to work with it. If you are familiar with it already, please jump to the next section.

GPG or GnuPG is an open-source implementation of PGP(Pretty Good Privacy), GPG allows you to sign and encrypt your data. That being said, it is a perfect tool to handle secrets.

Getting started

First, install the GPG via brew, brew install gpg. Then add dotgpgand dotgpg-environment to your Gemfile or simply run gem install dotgpg dotgpg-environment.

Initialisation

When everything is installed, it is time to initialise the GPG in the project. Simply execute dotgpg init in the project directory to start.

GPG will generate a private and public key. The public key is saved in the .gpg folder under your email visible to everyone. This key is used with the keys of other developers to create the hash for encrypting. The private key is saved in ~/.gnupg and is protected by a password.

Creating encrypted secrets

To test the creation of a secret file you can just do: echo foo | dotgpg create secrets.gpg, you will be asked for a password and then the hashed secret file will be created. The real secrets will be added later in the article via Mobile Secrets.

Adding more developers

To add a developer into the authorised group, the developer needs to provide a public key from his machine, simply dotgpg key will print the key. This key must then be added by the already authorised person via dotgpg add .

GEM: Mobile Secrets 📱

Mobile Secrets gem is using XOR cipher alongside with GPG to handle the whole process. Run gem install mobile-secrets to install it.

If you have not initialised the GPG yet you can do so with Mobile secrets as well by: mobile-secrets —-init-gpg . .

Let’s start with creating a template:mobile-secrets --create-template

MobileSecrets.yml

In the MobileSecrets.yml edit the configuration to the project needs. Unfortunately, at the time of writing this article only supported language is Swift. Hopefully, Kotlin will be added soon, PRs are highly welcomed. :)

Import finalized secrets mobile-secrets --import ./MobileSecrets.yml to secrets.gpg

Finally, we can run: mobile-secrets --export ./Output/Path/ to export the swift file with obfuscated secrets.

Mobile Secrets exported Swift source code
Exported Swift secrets source file

The ugly and brilliant part of the code

What happened behind the hood? Out of the YAML configuration, the secrets were obfuscated with the specified hash key and converted into bytes. Therefore, we ended up with an array of UInt8 arrays.

The first item in the bytes array is the hash key. The second item is the key for a secret, the third item contains the obfuscated secret, fourth is again the key and fifth is the value, and so forth.

To get the de-obfuscated key just call the string(forKey key: String) function. It will iterate over the bytes array convert the bytes into a string and compare it with the given key. If the key was found the decrypt function will be called with a value on the next index.

Since we have [[UInt8]] mixed with the hash, keys and obfuscated secrets it would take an extreme amount of effort to reverse-engineer the binary and get the algorithm. Even to get the bytes array of arrays would take a significant effort.

Fastlane + Automation

Mobile Secrets can also be used as a Fastlane plugin that provides encrypt_secrets and decrypt_secrets actions. The difference between the Fastlane plugin and the Mobile Secrets gem is that the plugin runs in non-interactive mode.

Simply a password can be passed from the CI as an environmental variable and then used within the Fastlane action to decrypt the secrets so as the private key associated with the account in GPG.

Mobile Secrets as a Fastlane plugin

To install mobile secrets as a Fastlane plugin simply add gem "fastlane-plugin-secrets" to the Pluginfile. The encrypt_secrets action can be used to create the secrets.gpg file. After it was successfully executed the action should be deleted. The config keys can be later edited via dotgpg edit with the GPG authorised person.

Since the secrets are now automatically generated, they should be ignored in the .gitignore. Authorised developers will decrypt the secrets with their password and unauthorised can use empty: true inside the decrypt_secrets action to generate the secret source file without the secrets.

Improvements

GPG uses pinentry, a program that asks interactively in a secure way for your password and then sends it back to GPG. It is quite unfortunate to do it every-time the script is executed. Luckily, there is a pinentry-mac program who is saving the password into a keychain. That being said, the password needs to be set only once and then it is read from the keychain.

The pinentry-mac can be installed via brew install pinentry-mac.To change default pinentry program for GPG, add this line:pinentry-program /usr/local/bin/pinentry-mac for Intel-based Macs or pinentry-program /opt/homebrew/bin/pinentry-mac for M1-based Macs to: ~/.gnupg/gpg-agent.conf. Create the gpg-agent.conf if it is not in the directory. A restart of the GPG process is required after this change. Simply ps -x | grep gpg and then kill the GPG process. The pinentry-mac should appear for the next GPG action.

In conclusion

Handling secrets in the project is very important. In this article, I showed a way of dealing with secrets the most secure way I could think of. Mobile Secrets and GPG are awesome tools that can be easily integrated into your project.

Credits go to Joerg Nestele, who worked with me on Mobile Secrets.

If you like what you read please share and give a clap or fifty. :)

If you liked this article and want to know more then please have a look at my book Modular Architecture on iOS and macOS — Building large scalable iOS and macOS apps and frameworks with Domain Driven Design where I keep the latest development practices I learnt along the way. Or simply get in touch on LinkedIn.

--

--

Cyril Cermak
Advanced iOS Engineering

iOS Engineering Lead @ Porsche AG. Setting goals and achieving them in AchieveMe.