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
ejsonfile somewhere in the repository. This is an encrypted JSON file using Shopify’s
- Create a
binfolder at the root of the repo and add this ruby script to the
bin/secretsfile to the repo. Remember to run
chmod +x bin/secrets
- Add a
Build Phaseto the iOS app called
Decrypt Secrets, the content of the script should simply be
- Add a
Secrets.swiftfile with these contents:
- Add a Plist file called
Secrets.plistbut leave it blank for now. Add it to Github in this state.
.gitignorefile so secrets we write to it aren’t pushed to Github at all.
- You can accomplish the above with this command, changing out
Secrets.plistto 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
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.