Secret Management
Storing secrets like API keys or tokens directly in an app's binary can be risky, but simple obfuscation techniques can add a layer of protection against casual reverse engineering. One such method is XOR encryption, which scrambles the secret using a key, making it harder to extract from the binary through static analysis. While this approach isn’t foolproof, it provides a lightweight security measure without requiring runtime retrieval.
Use Cases
- API Keys & Authentication Tokens: Protect sensitive credentials embedded in your app
- Feature Flags & Unlock Codes: Secure premium feature activation without plaintext exposure
- Configuration Values: Obscure sensitive configuration parameters
Advantages & Limitations
Advantages | Limitations |
---|---|
Minimal implementation complexity | Not cryptographically secure (can be reversed with effort) |
Negligible performance impact | XOR key must also be protected from exposure |
Prevents trivial binary scanning | Inappropriate for high-security secrets (e.g., cryptographic keys) |
Works offline without external dependencies | Should be used alongside other security measures |
WARNING
Never rely solely on obfuscation for critical secrets. Use proxy keys that can be revoked and rotated by your backend. Design your security architecture assuming secrets may eventually be exposed.
Implementation
- Create a Swift File
- In Xcode, go to File > New > File From Template...
- Select Swift File, name it
TokenManager.swift
, and paste the following code:
import Foundation
class TokenManager {
private func xor(data: [UInt8], with key: [UInt8]) -> [UInt8] {
return data.enumerated().map { $0.element ^ key[$0.offset % key.count] }
}
func generateObfuscatedToken(token: String, XORKey: [UInt8]) {
let tokenKeyBytes = [UInt8](token.utf8)
let obfuscatedBytes = xor(data: tokenKeyBytes, with: XORKey)
print("Obfuscated token: \(obfuscatedBytes)")
}
func decodeToken(obfuscatedToken: [UInt8], XORKey: [UInt8]) -> String? {
let decryptedBytes = xor(data: obfuscatedToken, with: XORKey)
let data = Data(decryptedBytes)
return String(data: data, encoding: .utf8)
}
}
- Generate Obfuscated Values (Development Only)
- In
MainViewController.swift
- Decide on a random XOR key, this is an array of [UInt8] numbers.
- In the
func viewDidLoad()
add the code below and run the app. TokenManager
will print out the obfuscated token in the Xcode console.
- In
// XOR key used to create secrets (must be random)
let secretsKey: [UInt8] = [0x31, 0x7C, 0x9A, 0x4E, 0x0F, 0x85, 0x6D, 0x2B]
let tokenManager = TokenManager()
tokenManager.generateObfuscatedToken(token: "SECRET", XORKey: secretsKey)
WARNING
Remove this code before production deployment
- Expose Secrets to JavaScript
- In
MainViewController.swift
store your XOR key as a class property and use it to decode obfuscated values at runtime:
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
// Class properties
private let secretsKey: [UInt8] = [0x31, 0x7C, 0x9A, 0x4E, 0x0F, 0x85, 0x6D, 0x2B]
// ...existing code...
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let tokenManager = TokenManager()
// Decode obfuscated values (replace with your actual obfuscated bytes)
let mySecret = tokenManager.decodeToken(
obfuscatedToken: [98, 57, 217, 28, 74, 209],
XORKey: secretsKey
)
// Inject values into JavaScript context
let javaScriptConstants = """
const __MY_SECRET__ = "\(mySecret ?? "")";
const __DEBUG_MODE__ = \(MainViewController.debugMode);
const __DEVICE_NAME__ = "\(UIDevice.current.name)";
// ...other constants...
"""
evaluateJavascript(javaScript: javaScriptConstants)
// ...other code...
}
}
Now when the app runs the __MY_SECRET__
global constant will be available to the JavaScript.
Best Practices
- Randomize XOR Keys: Use truly random values for your XOR key, not predictable patterns
- Layer Security: Combine obfuscation with additional protections like code guards and integrity checks
- Monitor Usage: Implement server-side monitoring to detect unusual API key usage patterns
- Plan for Compromise: Design your backend to quickly revoke and rotate compromised secrets