Search code examples
iossecuritycryptographyios9

Does Apple have a method in iOS to securely provide the services from a public/private key pair to all instances of the same iOS app on 9.3 or later?


What is a valid method to securely provide all instances of an app across different devices with a public/private key pair that would be used both to encrypt and decrypt data records created any of those devices, such that any instance of the app on any device can decrypt the records encrypted with those keys?

In particular, this is for a cloud-based iOS app on 9.3 or later that stores extremely sensitive information in some proprietary cloud-based servers. We don't want our own cloud server having the private key however, to eliminate the possibility of the key being leaked should our main server get hacked.

I was hoping there'd be a way for example to have Apple inject a common key to the system keychain at app install, so that no human outside of Apple's security fortress itself could see the private keys, and it would be as secure as the device's system keychain.


Existing options that I know about for storing a private key that would be accessed across all instances of the same app:

(1) Hard-code a key-pair right into the app. Downfall: If someone gets access to the code repository, and/or sufficiently decompiles the app, they can reconstruct the private key.

(2) Have the device connect to iCloud via CloudKit and pull down the key-pair from the app's public container, then store it into the system keychain. Downfall: an attacker who compromises the right AppleID credentials could gain access to everything needed to decrypt the data. Also, Apple's latest security white paper says an app's public iCloud container is "not encrypted".

(3) Have the app require a configuration profile be installed that contains the certs. Downfall: the source of the configuration profile could be owned by an attacker.

(4) Combination of 1 through 3: assemble the private key at launch time from a bunch of bits and pieces that are hard-coded, come from iCloud, and come from some. Downfall: less downfall by making an attacker jump over more hurdles is not no downfall.

(5) Is there a five?


What I want ideally is an Apple-provided way to inject a special key-pair into the system keychain of the device at the time of app install, such that that:

  • the app itself has role in the key injection, which would be done prior to the app launching

  • the app itself has no direct access to the key pair

  • all the app can do is pass things to a system framework to handle signing/encrypting/decrypting operations that rely on these keys

  • this key-pair is not available to versions of the app that are not signed by Apple (i.e. beta/dev/QA versions rely on proxy certs)

  • all copies of the app the same results from the same key-pair

  • the developer can reset the key-pair, at which point subsequent updates of the app can have the system do a one-time transition of data from being encrypted with the old key to being encrypted with the new one

... does Apple provide such a method? Or anyone else?


Solution

  • EDIT: More details on the method.

    Step 1: Use OpenSSL to generate a set of keypairs, K1 through KN, where N is the total number of layers of encryption on the data, with the following command:

    openssl genrsa -aes256 -out NPrivateKey.pem 2048
    
    openssl rsa -in NPrivateKey.pem -out NPrivateKeyOpen.pem -outform PEM
    
    openssl rsa -in NPrivateKey.pem -pubout -out NPublicKey.pem -outform PEM
    

    Step 2: Split up each key into a number F of separate files. These are the fragments.

    Step 3: Encrypt each fragment of KN with KN+1 (K1 does not get encrypted).

    Step 4: Create strategies S1 through SN for shuffling the order of the fragments of K1 through KN such that they can be algorithmically recombined into the original key by the app at runtime, assuming it knows N for a given K (how it knows N can be handled through an entirely separate process, if desired). This step allows for the fragments to exist in memory in a shuffled form for an additional degree of obscurity (for what it's worth). The goal here is simply to erect as many hurdles as possible in the path of a would-be attacker. For example if any aspects of the key were stored together on disk somewhere (for example in a repo or corporate key vault, etc.) this would tend to defeat simple attempts to scan a data volume, for example, using a regex sequence to identify keys stored on disk. If we are defeated, let it not be by a script kiddie—know what I'm saying?

    Step 5: Create a strategy D1 through DN for the distribution of the fragments such that the app can collect all the fragments at runtime.

    Step 6: Should any fragments be stored in the app's code itself they should be stored using the strongest possible form of obscurity, e.g. a set of purely algorithmic function calls that can output the key but that store no part of the key as a string literal. The strategy used for this obscurity would be O1 through ON, and which obscurity strategy is used for which fragment for which key is also shuffled according to strategy OS. (The use of method swizzling and dynamic runtime features of a language like Objective C can be useful here in creating some fun tricks to defeat anyone who gets a debugger attached to tell what the heck is going on; of course the distribution builds should always reject debugger connections, but that is out of scope for this answer.)

    Step 7: All sections of the codebase implementing any aspects of the above strategies or storing any obscured key fragments should never ever appear in any commits to the primary code repository for the project. Git filters should be used in tandem with scripts (time for fun with Swift shells scripts!) to smudge and clean all such code. A completely separate repository should be maintained on an encrypted drive stored in a physical safe, that is only brought out when the relevant static library/objects need to be recompiled. That way even if someone gains access to the github account (or whatever) they will not gain access to the strategies or fragments.

    Step 8: Do not store any keys in the system keychain. We have seen iOS's system keychain be compromised before. Other answers on StackOverflow can guide you on how to use a private key to decrypt data on iOS without the private key ever going into the system keychain. Perhaps this is a debatable point since the key could be deleted out of the keychain as soon as it's put in there, and for extra obscurity, you could do like we did and only store some of the keys in the stack in there, and store some of the encrypted fragments as if they were password hashes, etc. (all just more obscurity to lead people down false paths and confusing mazes that might try to break it).

    Again, of course I have no doubt that it's technically possible to defeat this (or any) form of encryption; however the goal is simply to provide as steep of a hill to climb as is reasonably possible for a would-be attacker, yet ultimately have multiple clients be able to share the same private key.

    Please refer to the article, Secure Key Distribution Using Fragmentation and Assimilation in Wireless Sensor and Actor Networks, which I discovered after developing the above solution. Ghafoor et al.'s "KDFA" method is in many respects similar to what I have described above.

    Also, please note that I'm sure anyone implementing such a solution as this will realize that there are many points in the process where additional layers of obscurity and encryption can (and should) be interjected; the goal of this answer is merely to describe the broad strokes of the method. The devil is in the details, and precisely how this method is implemented could make it more, or less secure.

    Also, of course, I believe it goes without saying that all network-based communication involved in the distribution strategies must be secured with TLS 1.2 w/PFS (or whatever the new best thing is when ever you are reading this in the future). Of course, further individual-client-specific private keys should be used on top of the shared private key. Of course, all the normal best practices in every regard are observed.

    However the issue of how to distribute the same key to a group of devices such that only the clients, but not the server, can decrypt the data, is the problem, even if we follow all the best practices. What we have seen time and time again is that no matter how secure clients are, if someone owns the server then the fox gets all the eggs in the hen house. However if the server is merely a storage facility for totally encrypted data and the client employs a sufficiently dynamic and complex method to encrypt/decrypt that data, then it tends to help the concern of "what if the server gets owned" and "what if the code repository gets owned" etc.

    Now if there are any major flaws in the above approach then of course, I would love to hear that so it can be improved.


    I just ended up taking two keypairs. One of them I used to encrypt the other one. Then I broke up the encrypted form into many shards and stashed them all over the place... in the cloud... on servers... in obfuscated strings on my app... etc.

    None of the strings involved in the encryption or keys is in string or binary format in the compiled app. It is all generated programatically in a quite obfuscated fashion.

    At runtime we use arcane math functions to build method names that call hidden methods around the app to assemble the first key, then we go get the encrypted shards of the second key from all over the internet and decrypt them with the first key, then we use the second key to decrypt the important client data.

    Then we use a special sauce method where none of the code that's involved in this obfuscation is anywhere in our repository. It gets loaded in dynamically at a special time. :D That's all I will say about that.