Secrets Management in iOS Applications
Secrets in iOS applications can be a bit of an issue, especially when you intend to make a project public. The problem here is that there is no good way to store and use secrets in a compiled application, so you end up with plain-text secrets littered about the codebase.
There are numerous solutions that exist but these seem to always end up with the conclusion of “whatever, just add it to the repo” or suggest building a full user authentication system where they log into your system via the application. The former is obviously an issue for an open sourced repo (less so for a private repo) and the latter adds a lot of overhead when sometimes you just want to use the Mapbox API.
In this article, I intend to tackle the issue of storing plain-text secrets in the repo.
Solving the secrets issue
For this problem, I found a method using encrypted JSON and Plist parsing to work quite well. Below, I explain how I set this up and how it works.
- Store secrets in an
ejson
file somewhere in the repository. This is an encrypted JSON file using Shopify’sejson
library. - Create a
bin
folder at the root of the repo and add this ruby script to thebin/secrets
file to the repo. Remember to runchmod +x bin/secrets
- Add a
Build Phase
to the iOS app calledDecrypt Secrets
, the content of the script should simply bebin/secrets
- Add a
Secrets.swift
file with these contents:
- Add a Plist file called
Secrets.plist
but leave it blank for now. Add it to Github in this state. - Add
Secrets.plist
to the.gitignore
file so secrets we write to it aren’t pushed to Github at all. - You can accomplish the above with this command, changing out
Secrets.plist
to use the path of your actual plist file:
git add Secrets.plist
git commit -m 'Add blank Secrets.plist'
echo "Secrets.plist" >> .gitignore
git update-index --assume-unchanged Secrets.plist
- Done!
How does it work?
The system works by decrypting the secrets.ejson
file to a plist file that is compiled into the application. The Secrets
class reads this plist file from the bundle and into a Swift dictionary. The entire application can now access the secrets using Secrets.secrets()['my_key'] as? String
.
This means you no longer have to store unencrypted keys in the repo — a huge win for security.
On build, the secrets are decrypted and stored in plaintext in the app bundle— this doesn’t avoid that. But it does limit the exposure of secrets in the repo by not storing them in plaintext outside the app bundle.
The only caveat I have seen so far is that all users must now have ejson
installed and place the private key that corresponds to your ejson file’s public key on their file system for the app to work, but this is not really a major issue if you have access to the developers of the app.