max
max

Reputation: 851

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

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

Upvotes: 23

Views: 8703

Answers (7)

Ben Lachman
Ben Lachman

Reputation: 3100

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").

Upvotes: 4

natbro
natbro

Reputation: 1038

I have been wondering about this, too, and several potential solutions come to mind based around the premise that what you want is to get a user/pass secret key into your app's KeyChain (which is quite strongly secured by iOS and the hardware) and pull it for use as-needed:

  1. distribute the secret to your app using an app-specific iCloud ubiquity-container. this data should be excluded from backup to the local computer and is purportedly securely transmitted using hardware-level security to only non-jailbroken applications. pro's: it's not in your application at initial distribution, so harder to uncover, iCloud requires a non-jailbroken device, you can update your secret and it will synchronize to all your apps. con's: it's not really in the secure KeyChain, which means that it can likely be sniffed out on the filesystem if iCloud syncs and then the device is jailbroken.

  2. deliver the secret to your app as a piece of free app-store-hosted in-app purchase content. when it is delivered (securely by the app-store, only to non-jailbroken devices) to the app, transfer it into the keychain. pro's: it's not in your application at initial distribution, so harder to uncover, app-store requires a non-jailbroken device. con's: harder to change the secret for all of your installs quickly, even a free app-store purchase may require user authentication, which is troublesome UX.

An ideal solution would be if we could somehow bundle secrets (a KeyChain key/value dictionary) right into the app when we submit it for distribution, the app-store would strip these and deliver them securely to the OS for injection into the KeyChain during install, but out-of-band from the normal app bundle sync'd with your desktop machine and iTunes, and they would not appear in the binaries. Barring Apple adding such a feature, I think there is no truly solid solution, though.

Upvotes: 20

Andrew Kurinnyi
Andrew Kurinnyi

Reputation: 767

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.

Upvotes: 2

Michael Haephrati
Michael Haephrati

Reputation: 4225

If you hardcode the key inside the App, there are more chances to hack it, so it would be better if the App sends a request to the server each time and receives the key from the server.

Upvotes: -2

Nubis
Nubis

Reputation: 76

I'm afraid it's not possible to do that. But as far as I know apple will make sure no other app is spoofing your app's secret. If it's a jailbroken phone, then the user is in a way taking full responsibility, and possible damage should be limited only to the jailbroken phone user's data.

Upvotes: 6

Jason
Jason

Reputation: 28600

Since the attacker would be in complete control of the client, the only method would be security through obscurity. You can implement a challenge/response model, but you must make it impervious to many other factors (replay attacks, etc).

This question contains one approach to hide a secret key in binary code.

Don't assume that by simply using https you can't have packet sniffing. What if the attacker changed their URLs inside your executable to point to their server? Then they can act as a relay.

A better way would be to provide some identity management inside the app (user picks a username, password is user/machine generated), and use those credentials for your service calls.

Upvotes: 3

jv42
jv42

Reputation: 8593

An usual answer is implementing "hand-shaking" protocols, where the server sends a "challenge" and the client must provide a valid answer.

This enables a lot more security than a hardcoded answer, but require a smart algorithm (avoiding standard hashes for instance).

Upvotes: 0

Related Questions