Storing Secret Keys

Often when writing iOS apps, we need to store secret keys for accessing APIs and the like, so that your backend can verify that requests are actually coming from your app. And usually, it's worth keeping these keys secret from most prying eyes, which is what this article is about.

Disclaimer: The obfuscation techniques described here are only strong enough to fend off most crackers from finding your keys. It's impossible to protect securely against an expert with intimate knowledge of LLDB, so don't rely on these techniques as acting as anything more than a strong discouragement to someone who is after your keys. This is, after all, security by obscurity. I disclaim any and all liability if you follow these instructions.

Update: Here's a talk I gave on the subject at my local Cocoaheads meetup:

Keys that aren't sent as part of requests, such as secret keys used for HMAC generation, are particularly suitable for techniques as described below. Keys that are sent as part of requests are basically public, and can be snooped trivially using an app such as the Charles debugger, so I wouldn't bother obfuscating those keys. The exception is that if you're relying on SSL certificate pinning to ensure that your messages to the server cannot be decrypted by an installed root certificate MITM (eg Charles with SSL forwarding enabled), then it'd be worthwhile.

Literal strings vs hex

Firstly, you do not want an attacker to be able to decrypt your app's binary, and run strings MyApp from the command line to simply dump all the literal strings in your app, and peruse them until they find something that looks like a key. So, I recommend you avoid the following:

static NSString *kMyAPIKey = @"abcdef123456"; // Really easy to find using the 'strings' command line tool.

Instead, you might consider storing your keys as literal hex values, which hides them from 'strings', like below:

unsigned char myApiKey[] = { 0xDE, 0xAD, 0xBE, 0xEF }; // Hidden from 'strings' if you're lucky, but not from a hex editor / MachOView.

Update: Since writing this article, I've been informed that if you do the above, you can still find the key using strings. It didn't in my testing, so I guess it depends if a zero appears after your data in the executable, which strings would take to mean a null terminator. In any case, you should read further in this article beyond this basic step. For posterity's sake, i'm leaving the following paragraph in, however you may feel free to ignore it:

The above is a little better, your key is now hidden from the simplest of attacks. However, the raw key is still in the binary, and can be found with either a hex editor or (more realistically) MachOView. Searching with a hex editor would be like looking for a needle in a haystack, but MachOView can open your app binary and allow someone to search only in the constants section, which makes it a lot faster. For instance, with MachOView, I was able to find a key in one of my app binaries in a few minutes, just by scrolling until I saw an interesting-looking bunch of values.


The next step is to obfuscate the key, so the key cannot be found simply by looking at the static binary. A simple technique I've used is to XOR the key against some value that can easily be replicated at runtime, so you're only storing the obfuscated value in the binary. In Objective-C development, a good value to use to XOR against can be as simple as a SHA hash of one of your class names. I chose to use a class name because it does not require any further literal to be stored in the binary. Just be sure to choose a SHA variation that has an output the same length or longer than your secret key. An example implementation is below:

#include <CommonCrypto/CommonCrypto.h>

unsigned char obfuscatedSecretKey[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF };

// Get the SHA1 of a class name, to form the obfuscator.
unsigned char obfuscator[CC_SHA1_DIGEST_LENGTH];
NSData *className = [NSStringFromClass([ChooseOneOfMyClassesHere class])
CC_SHA1(className.bytes, (CC_LONG)className.length, obfuscator);

// XOR the class name against the obfuscated key, to form the real key.
unsigned char actualSecretKey[sizeof(obfuscatedSecretKey)];
for (int i=0; i<sizeof(obfuscatedSecretKey); i++) {
    actualSecretKey[i] = obfuscatedSecretKey[i] ^ obfuscator[i];

... now you can do something with actualSecretKey ...

With the above in place, any attacker would have to either figure out how your code works with a disassembler (really hard), or they'd have to attach a debugger and hook into functions until they found where you actually do something with the secret key once it is in the clear (less difficult, but still requires quite an advanced attacker).

Now there's a simple trick to generate your obfuscated key in the first place: Put your real secret key into obfuscatedSecretKey, put a breakpoint after it generates the actualSecretKey, dump the value of actualSecretKey, and use that as your obfuscatedSecretKey. This works because XOR is a reversible process.

Disabling debug sessions

To somewhat mitigate the risk of crackers attacking your app with a debugger (LLDB or GDB), you can insert some code in your app that makes it crash as soon as it detects a debugger attached. The iTunes app uses this technique, you can read about it here.

To achieve this, here is a sample implementation for your main.m file, from the iPhone wiki:

#import <dlfcn.h>
#import <sys/types.h>

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif  // !defined(PT_DENY_ATTACH)

void disable_gdb() {
    void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);

int main(int argc, char *argv[]) {
    #if !(DEBUG) // Don't interfere with Xcode debugging sessions.

    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([MyAppDelegate class]));

Now, of course, a sufficiently talented cracker can work around this, however it's another obstacle I recommend placing in their way to make it more difficult.


So, to sum up, my recommendations:

  • Store your secrets as unsigned char myVar[] = {0x00, 0x11, ...} instead of @"SomeString".
  • Obfuscate by XORing against the hash of a class name.
  • Disable GDB/LLDB at the start of main().
  • Use SSL certificate pinning if you are sending your key as part of the request, as opposed to using it in an HMAC.
  • Don't rely on any of this preventing a determined, skilled cracker, it's a deterrent against the 99%.

Footnote: As mentioned before, I disclaim responsibilities if you follow this article, as it is only security by obscurity.

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