Online services and APIs are an inseparable part of most apps. Often they require the use of a secret key to identify the subscribing client, usually is in the form of a long string of alphanumeric characters. Invariably, it would be a bad thing™ for a malicious user to get their hands on this key. Perfect security is impossible, but there are some simple steps you can take to make it more than trivially easy for snoopers to extract your API keys from your app.
strings: A Snooper’s Best Friend
There is a command line app called
strings that is designed to scan binary files and print out anything it thinks is a string embedded within. Here’s the description from the man page:
Strings looks for ASCII strings in a binary file or standard input. Strings is useful for identifying random object files and many other things. A string is any sequence of 4 (the default) or more printing characters ending with a newline or a null.
Here’s a tiny bit of the output when I pointed it at the Photos application binary:
Burst favoriting action doesn't currently support the 'none' option -[IPXChangeBurstFavoritesAction _revertFavoritingChanges] ***** Burst action: _revertFavoriting changes Will undo/redo for Keep Selection option Total: %ld. Trashed: %ld. Untrashed: %ld. Pick type set: %ld Will undo/redo for Keep Everything option Total: %ld. Fav: %ld. Unfav: %ld. Warning: burst == nil -[IPXChangeBurstFavoritesAction _setFavoritingOnVersion:stackPick:] Invalid state: version exists in both favorite and unfavorite sets for action. Burst Change - Favorite: %@ Burst Change - Unfavorite: %@ IPXChangeBurstFavoritesActionKey IPXActionAlertThresholdMessageGeneric IPXTestActionProgress -[IPXActionProgressController endModal] /Library/Caches/com.apple.xbs/Sources/PhotoApp/PhotoApp-370.42/app/spark/Source/Actions/IPXActionProgressController.m -[IPXActionProgressController performActionSelector:] Invalid selector -[IPXActionProgressController checkModalSession] Progress window still visible after action complete. Possibly hung? Action log: appIcon
In the case of the Photos app,
strings found 38675 string candidates. A lot of them were garbage, and there were literally thousands of Objective-C selectors, but there were also a lot of strings that were obviously never intended for user consumption. If it’s a string in your code, it will be found by
strings and you can bet that someone snooping for API keys has pattern matching schemes that will make them trivial to find.
The easiest way to prevent Strings from finding your API keys is simply to not include them as strings. However, do not think that putting a sequence of ASCII bytes into an array is going to help you, if your array’s bytes match the ASCII codes for the characters, you’ve just made a cumbersome string and it will probably still be detected as such.
A good first step for obfuscation would be to mutate those bytes in some way so that they don’t all fall within the ASCII alphanumeric range. The two simplest, non-destructive ways of doing this would be:
- Invert the bytes by subtracting them from 255. So, a value of 10 becomes 245 and a value of 50 becomes 205, etc. Note: this is identical to using XOR with a nonce of 255.
- XOR each byte with a single-byte “nonce”, which is just random number between 1 – 255 (XOR with 0 produces no change). XOR is a reversible operation: if you XOR with a given byte twice, you end up with your original value. In practice, you’d want to pick a nonce byte that has at least 3 of the 8 bits as 1s to ensure sufficient mutation of your API key bytes.
Then you would simply store the converted bytes in your app instead of the string and convert it back to the string by reversing the operation at runtime to produce the original string.
To be quite honest, either of these approaches is probably good enough. But if you want to be more thorough…
If you are using the single-byte XOR approach from above, your API key would be safe from a simple
strings search, but there are still only 254 ways you can possibly obfuscate the string and a really determined snooper might still be able to find it. Let’s make their job exponentially harder and use a multi-byte nonce!
The basis for this approach is a new Sequence type I created called
RepeatingSequence. The general idea is that it initializes with any collection type and returns the elements in sequence, wrapping back to the first element once the last one has been emitted.
This lets us use a sequence of random bytes instead of just one. I created a Playground that you can use to generate a multi-byte nonce and use it to encode a string. Then, just include the byte array it prints out instead of the string in your app.
[gist https://gist.github.com/JoshuaSullivan/92472caefc789554863d429764ad0b59 file=”StringObfuscationPlayground.swift”]
Of course, that byte array isn’t going to do you any good unless you can turn it back into a string. Here’s a struct with a static method that does just that:
[gist https://gist.github.com/JoshuaSullivan/92472caefc789554863d429764ad0b59 file=”ObfuscationDecoder.swift”]
This code is pretty simple to incorporate into your workflow and can give you a lot of peace-of-mind that your app’s API keys won’t be trivially easy to steal.