Every now and again, an iOS app will have cause to hide some information such that it’s very difficult to find by looking at the binary. Be it some form of DRM key, or crypto keys, or API keys, or whatever - sometimes you simply must hide data in your binary. Below I’ll tell you my latest favourite trick to do so (I wrote years ago a separate technique, but I think this is simpler and more flexible).

Before I continue, be aware you should authenticate users, not authenticate an app. It’s mathematically impossible to hide data perfectly yet still be able to access it from your code. A determined hacker will be able to find it. However, this technique will help prevent the bots that are running scripts over your binaries from easily stealing your API keys. I recommend talking to a security consultant about potentially re-architecting your systems so that it isn’t necessary to rely on eg a hidden API key. This article is provided with no warranties!

Ok so that out of the way, here’s the gist: Your data (C for cleartext) is converted to data, and a same-length amount of random data R is generated. The two are XORed to produce X. R and X are then stored in your app. Thus someone running ‘strings’ on your app won’t find anything valuable, it’ll simply look like a bunch of random garbage. Unfortunately it’ll still be vulnerable to anyone capable of attaching a debugger, but there’s no technique that can solve for that - what you’re doing here is raising the bar.

Here’s a Playground for generating R and X:

import Foundation

// You can specify your data as bytes like so:
//let clear: [UInt8] = [1, 2, 3, 4...]

// Or convert it from a string like so:
let clear: [UInt8] = [UInt8]("My_API_Key_Here".data(using: .utf8)!)

// Generate the random data.
let random: [UInt8] = (0..<clear.count).map { _ in UInt8(arc4random_uniform(256)) }

// Xor them together.
let obfuscated: [UInt8] = zip(clear, random).map(^)

print(obfuscated + random)

Run this a few times, and check that you’re getting different random gibberish each time. Copy the output to your app like so:

let obfuscatedApiKey: [UInt8] = [1, 2, 3, 4...]

And use the following in your app to de-obfuscate the key:

extension Array where Element == UInt8 {
	var deobfuscated: [UInt8] {
		let a = prefix(count / 2)
		let b = suffix(count / 2)
		return zip(a, b).map(^)

Some convenience accessors for your obfuscated keys might look like so:

struct MyConstants {
	private static let obfuscatedKey: [UInt8] =
		[135, 89, 20, 175...]

	/// My API key! Obfuscated so won't be too easy for hackers to find.
	static var key: String {
		return String(bytes: obfuscatedKey.deobfuscated, 
			encoding: .utf8)!

Thanks for reading, and have a great week!

Photo by Dayne Topkin on Unsplash

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

iOS Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]

 Subscribe via RSS