Effortless Secrets Management for Swift projects using Code Generation. A simpler approach than using GYB files as outlined by the NSHipster article on Secret Management on iOS using Swift Build Tool Plugin (Swift 5.6+).
- Provides a convenient way to keep secrets out of source code
- Encrypts Secrets when they are at rest in your applications binary
- Provides convenient access through global
Secrets
- Run as manual Script, SPM plugin or Xcode plugin
- Less than 250 lines of Swift
Use a .env
bash script to export Secrets you want available to your source code.
#prefix ORG
export ORG_API_CLIENT_SECRET=hrFL6LpsGQPsEQdipfTSlosI6topYTfhLNCfIvbfUz5r6nc72DMRbLL3msjuAFnY
export ORG_ANALYTICS_KEY=dak37Qv5KGwNsQxVJxjJY2OtbUnGKXlm3mkDApSRfrAsTHQdFRSEfrA9yin5T4YT
export ORG_BACKEND_KEY='KwphtrRhgOXcRd=p!73QnrQLuOj=rx8edJhMy52sWeQPKMxOxA8hNcDrG9=XRvAw'
export ORG_LOGGER_KEY='oW7YQKg2eNcVjzRdmCtmgCCSBp2dpJlL5NC-Pj!asS5XdPG/--R2hE?/=I/TlotP'
Using this plugin the following Swift code is generated and available directly to your targets source code, no need for an import.
// This file is automatically generated
import struct Foundation.Data
private func secret(_ secret: String) -> String {
let data = Data(base64Encoded: secret)
guard let data else {
fatalError("Failed to decode a secret!")
}
func decrypt(_ data: Data) -> String {
let key = Data(base64Encoded: "JbiOFqC+jH3l8pwCLE4Nca4f19M7YAbeTUo7rhnSSG7ctZMlc+dg5FI9o3zrSbCgFLtDd0uC9EcCC+jd6hlVDA==")!
var output: [UTF8.CodeUnit] = []
for (offset, ch) in data.enumerated() {
output.append(ch ^ key[offset % key.count])
}
return String(bytes: output, encoding: .utf8)!
}
return decrypt(data)
}
enum Secrets {
static let apiClientSecret = secret("TcrIWpby/A6io8xxaR9pGN55g4BXD3WXez5U3kCGLgaQ+9BDOpECggdHlg7dJ9OXJv8OJSnOuHRveIKoq187VQ==")
static let analyticsKey = secret("QdnlJZfv+kiutetMXx91J+RnvZliUkmqLx9V6VKKJAPv2PhhMpcztjRP4g+/AeHEUukQMi3wtX57Yobovi0MWA==")
static let backendKey = secret("bs/+ftTM3hWCvcRhfiowAY8o5IJVEleSOAVRk2uqcAu4//toCtJSlwVY8iygBMjvbPp7HwXhsDVFMtWFuG8Uew==")
static let loggerKey = secret("Su+5T/H160+AvP9URjRfFcNco75cI0WNDzoJymmYJCLp+9AII41BhSFuliSPGfePOZYRRSPHy2g/QseJhnYhXA==")
}
Requires Swift 5.6 (Xcode 13.3+)
Create a .env
file in your root directory (alongside Package.swift
or your *.xcodeproj
).
You can define a prefix to strip from all your exported keys with #prefix
. See Usage
for an example .env
file.
- Add SecretsManager package
- When prompted to Choose Package Products for SecretsManager don't select any products
- In Targets > Build Phases add
SecretsManagerPlugin
to Run Build Tool Plug-ins - Build, triggering a prompt to trust the plugin
- From the issue navigator you can goto the plugin and read the source code before trusting
Add the following to your Package.swift
files dependencies array:
.package(url: "https://github.com/vdka/SecretsManager.git", from: "1.0.0"),
And to the targets Secrets should be available to, after their dependencies
:
plugins: [
.plugin(name: "SecretsManagerPlugin", package: "SecretsManager"),
]
When running in Xcode cloud you will want the secret values to come from the actual environment. In
order to tell the plugin which keys to read from the environment from you can create a .env
file
that exports only the keys without associated values.
#prefix ORG
export ORG_API_CLIENT_SECRET
export ORG_ANALYTICS_KEY
export ORG_BACKEND_KEY
export ORG_LOGGER_KEY
This package doesn't aim to keep your Secrets safe from intentional attacks. It's aim is to make it convenient to adopt best practice Secrets Management in Swift projects. It does this by ensuring keeping your secrets out of your Source Code doesn't sacrifice usability, and that when compiled into your application, they are not stored in plaintext. Remember Client Secrecy is Impossible.