How to securely include secret key/signature in iOS/Cocoa apps

Viewed 8208

a long time ago I learned that this is a bad idea. Please don't try what I'm asking below

I want to include a secret key into an iOS app so that the app can "prove" to a certain server that a request is coming from the app itself and not some other system. I know that simply hardcoding a secret key into the code itself is very vulnerable as anyone can jailbreak their phone and attach GDB to my app's process to get the key. Are there any more secure ways of doing this? Is it possible of sufficiently obfuscate the key as to make this near impossible?

I believe that this is a similar problem to serial number validation. Unfortunately, that seems to get cracked regularly and easily. Are there any solutions to this?

All communication with my server will be done with HTTPS so at least sniffing/man in the middle attacks shouldn't be a concern.

Thanks, M

7 Answers

I agree with @Nubis that there is no 100% bulletproof way to do it.

However, this library seems like a pragmatic solution to the problem:

https://github.com/UrbanApps/UAObfuscatedString

It probably won't save you from a highly motivated attacker, but it won't make their life easy.

As an update to @natbro's answer, a great solution is now to use CloudKit. With this method you'd create a record in the public database and each instance of the app would grab it on start up. Since CloudKit is based on iCloud login, it has most, if not all, of the same safeguards that an app-specific iCloud ubiquity-container would have. There are two main differences:

  1. CloudKit is more deterministic when it comes to retrieving data. You know secrets/keys will be available when you fetch them from CloudKit.

  2. Data from CloudKit isn't synced to the device or cached in the app's container, it's retrieved on-demand and any caching is up to you, the developer (note: I'd suggest caching the keys in the Keychain).

Here's a quick snippet for retrieving a record from CloudKit.

import CloudKit

...

let publicCloudKitDatabase = CKContainer.default().publicCloudDatabase
let recordID = CKRecord.ID(recordName: "default") // or whatever you name it

publicCloudKitDatabase.fetch(withRecordID: recordID) { (record, error) in
    if let secretRecord = record {
        guard let secret = secretRecord["aKey"] as? String else {
            print("Unable to get secret")
            return
        }

        self.secret = secret // or somesuch
    }
}

Notes: You'll need to set up CloudKit as specified in the docs and then create a record that matches what your app is expecting or vice versa (in this case a record with recordName = "default" which contains a field with the key "aKey").

Related