I'm writing an iPhone application that requires its data to be encrypted. I've learned how to turn on encryption for files by setting the NSFileProtectionComplete attribute. I also know how to check the iPhone version to make sure they are running iOS 4.0 or better.
What I've realized though, that if the user has not chosen a passcode and has not specifically enabled data protection on the Settings > General > Passcade Lock screen then the data isn't actually protected at all.
I'd like to pop up a warning and tell the user that they must enable a passcode and turn on data protection (which requires a backup and restore on pre-4 iPhones), and then exit the application if they do not have a passcode and data protection enabled. I can't figure out anyway to find out the state of these settings though. All of the APIs I've found, such as "protectedDataAvailable" in UIApplication all pass with success if data protection is disabled.
Yes. Apple's iPhone, iPod touch, and iPad smart devices all support basic built-in encryption while a passcode is enabled. Macs also support their own form of data encryption. The encryption on Apple's iOS and iPadOS devices, such as the iPhone, iPod touch, and iPad, is called Data Protection.
Since the 3GS came out, Apple has consistently built 256-bit AES encryption into iOS devices. There's a unique identifier (UID) built into each phone's hardware, which automatically encrypts data stored on your phone — including messages, pictures, and call history — by default.
Disclaimer: This answer was valid until ios 4.3.3
If data protection is turned on, a newly created file will have a nil
NSFileProtectionKey
by default.
If data protection is turned off, a newly created file will have a NSFileProtectionNone
NSFileProtectionKey
by default.
Thus, you could detect the presence of file protection with the following code:
NSString *tmpDirectoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp"]; NSString *testFilePath = [tmpDirectoryPath stringByAppendingPathComponent:@"testFile"]; [@"" writeToFile:testFilePath atomically:YES encoding:NSUTF8StringEncoding error:NULL]; // obviously, do better error handling NSDictionary *testFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:testFile1Path error:NULL]; BOOL fileProtectionEnabled = [NSFileProtectionNone isEqualToString:[testFile1Attributes objectForKey:NSFileProtectionKey]];
iOS 8 (OS X Yosemite) introduced a new API/constant used to detect if a user's device has a passcode.
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
can be used to detect if a passcode is set on the device.
The flow is:
I have tested this on my iPhone 5S, first it returned true
, then I disabled the passcode in settings, and it returned false
. Finally, I re-enabled the passcode and it returns true
. Prior OS versions will return false
. The code works in simulator, returning true
on a machine with OS X password set (I haven't tested alternate OS X scenarios).
Also see sample project here: https://github.com/project-imas/passcode-check/pull/5
Finally, to my knowledge iOS 8 doesn't have a setting to disable data protection, so I assume this is all you need to guarantee encryption.
BOOL isAPIAvailable = (&kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly != NULL); // Not available prior to iOS 8 - safe to return false rather than crashing if(isAPIAvailable) { // From http://pastebin.com/T9YwEjnL NSData* secret = [@"Device has passcode set?" dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *attributes = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: @"LocalDeviceServices", (__bridge id)kSecAttrAccount: @"NoAccount", (__bridge id)kSecValueData: secret, (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly }; // Original code claimed to check if the item was already on the keychain // but in reality you can't add duplicates so this will fail with errSecDuplicateItem // if the item is already on the keychain (which could throw off our check if // kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly was not set) OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); if (status == errSecSuccess) { // item added okay, passcode has been set NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: @"LocalDeviceServices", (__bridge id)kSecAttrAccount: @"NoAccount" }; status = SecItemDelete((__bridge CFDictionaryRef)query); return true; } // errSecDecode seems to be the error thrown on a device with no passcode set if (status == errSecDecode) { return false; } } return false;
P.S. As Apple points out in the WWDC video introducing this (711 Keychain and authentication with Touch ID), they chose not to make the passcode-status directly available via API on purpose, in order to prevent apps from getting in situations they shouldn't be (i.e "Does this device have a passcode? Okay, great, I'll store this private info in plain text". It would be much better to create an encryption key, store it under kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
and encrypt that file, which will be unrecoverable if a user decides to disable their passcode).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With