Skip to content

A Swift Package to generate and use encrypted secrets in your app

License

Notifications You must be signed in to change notification settings

vdka/SecretsManager

Repository files navigation

SecretsManager

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+).

Features

  • 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

Usage

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==")
}

Setup

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.

Xcode Projects

Visual Guide

  1. Add SecretsManager package
    • When prompted to Choose Package Products for SecretsManager don't select any products
  2. In Targets > Build Phases add SecretsManagerPlugin to Run Build Tool Plug-ins
  3. Build, triggering a prompt to trust the plugin
    • From the issue navigator you can goto the plugin and read the source code before trusting

Swift Package Manager

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"),
]

Xcode Cloud

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

Security

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.