0

Assume a user enables Touch ID in my app. I need to save this state somewhere. I have two options: NSUserDefaults, and the Keychain.

  1. NSUserDefaults: The problem with storing a boolean fingerprintEnabled in NSUserDefaults is that this flag can be modified. So, an attacker with physical access to the device can simply edit this flag and bypass fingerprint authentication in my app, and gain access to user's secure offline data.

  2. Keychain: This is great, as it is encrypted and cannot be modified externally. However, keychain values are persisted across app installs/uninstalls. If a user uninstalls the app, perhaps because Touch ID isn't working properly, and re-installs it, it will still prompt for Touch ID, even though the new app installation should reset all state.

So where's the best place to store state on whether fingerprint authentication is required?

Snowman
  • 31,411
  • 46
  • 180
  • 303
  • In my company, we store the flags in the keychain. When the app reinstalls, we wipe the entire keychain. We also hash all keys in the user defaults. – Brandon Sep 16 '17 at 17:24
  • Why don't you have 6-digit passcode alongside with TouchID? – surfrider Sep 16 '17 at 17:29
  • When I implement such checks, I use TouchID to unlock a secret key stored in the keychain. This is much safer... See my answer below. – Michael Sep 16 '17 at 17:31
  • @Brandon how do you determine if the app is a re-install? – Snowman Sep 16 '17 at 17:48

2 Answers2

0

If you are so concerned about the security of data saved in NSUserDefaults why don't u use the combination of both NSUserDefaults and Keychain

Save a variable in NSUserDefaults lets say installed. This you'll set it to true in AppDelegates didFinishLaunchWithOptions. This you can use as a reference when should you reset the value of fingerprintEnabled in keychain.

Problem with saving it in key chain is you don't know when to reset it. The value in NSUserDefaults now will help you decide when to reset this value.

Because the value in NSUserDefaults will be removed on uninstalling the app, when u uninstall and reinstall the value of installed will be absent in NSUserDefaults now you can go ahead and reset the value of fingerprintEnabled in Keychain if present (Obviously this should be done in didFinishLaunchWithOptions before you set the installed).

On subsequent launches value of installed will prevent you from resetting the value of fingerprintEnabled.

I know this is not the most efficient approach as it uses both NSUserDefaults and KeyChain but solves the problem though.

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • Wasn't me. But so this is a decent answer. Sort of like what Brandon recommended in the comments? – Snowman Sep 16 '17 at 17:50
  • But then again, an attacker can modify `installed` and force the app to reset the Keychain, thus removing fingerprint authentication, no? – Snowman Sep 16 '17 at 17:52
  • @snowman : true that sir :) Not really sure how will u find whether its a fresh install without a key in NSUserDefaults though :( – Sandeep Bhandari Sep 16 '17 at 17:53
0

If using NSUserDefaults is unsafe, using the keychain is unsafe too, so I would use the NSUserDefaults, because it is easier to implement. Let me explain: If the user can just edit the user defaults and get access to the data, this means that the data is unencrypted. (Or at least, it's encrypted in a way that is easy to break if you have access to the source code of the app.) If the data is unencrypted and you use the keychain to store the boolean, the user can as well just patch the binary to omit the keychain-check. Or he may just find the unencrypted payload in the application binary and inspect it.

But now for something more constructive... :) The best way to implement Touch ID protection, is to use Touch ID to unlock a secret key, and encrypting the payload data with that secret key. If Touch ID is disabled, you just use a hardcoded key instead (and use some kind of basic runtime obfuscation, like base64_decode to make it not too easy for attackers.) You can store a boolean in the user defaults (e.g. "data_encryption_enabled")*. If it is YES, you ask the system for the secret key for the "data_access" item. You then decrypt the data with that key (I suggest to use AES128). If "data_encryption_enabled" is NO, you use "9fajw9e8" as the secret key instead. It's easy to see, that if an attacker manages to change "data_encryption_enabled" to NO, this will not unlock the data.

A few more things to consider:

  • When the payload is saved in a way that it can be backed up to iCloud, you should test if restoring the app from the backup to a different device makes the data unusable. If the data is stored only locally (e.g. either in "Caches", or in "Documents" with NSURLIsExcludedFromBackupKey set to YES/true) this is not an issue.
  • The algorithm that generates the secret key should be secure. (E.g. use SecRandomCopyBytes)

*) all strings in this example are made up by me, you can choose them however you like.

Michael
  • 6,451
  • 5
  • 31
  • 53
  • 2
    So this way if that flag is modified, an attacker gains access to the data, but the data is encrypted, so it's useless. I guess that works. – Snowman Sep 16 '17 at 17:49
  • @Snowman: exactly. Because there are considerably less people who are able to crack the iOS keychain than there are people who are able to crack the typical iOS app. – Michael Sep 16 '17 at 18:20