How can I find out if the iPhone user currently has a passcode set and encryption enabled?
Asked Answered
I

6

39

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.

Isochronize answered 27/9, 2010 at 17:55 Comment(10)
I would assume that the passcode lock is irrelevant to the running application so I guess it's not part of the SDK. If it were, it would probably be part of this API: developer.apple.com/library/ios/#documentation/uikit/reference/…Unworthy
It's extremely relevant to a running application because without a passcode, your data is not protected on the device. It would be a huge oversight by Apple if there is no way to tell whether your data is protected or not. It makes the new iOS 4 encryption pretty much useless for most enterprise applications sold through the app store.Isochronize
Most enterprises would (should) have a deployment profile pushed to all of the company iPhones to require the passcode. This isn't a program issue, this is a management issue. You really want to pop up a warning telling the user to turn on his passcode? Or better yet, refuse to run unless it's on? Users don't usually take well to being told what to do with their devices.Siam
You can't have a deployment profile for apps sold through the app store. This application is not for company managed phones. The current security model is confusing for users it is very difficult for them to tell if their data is secure. For example, if you have an iPhone 3GS, even if you have upgraded to iOS 4 AND turned on a passcode, your data is NOT protected unless you have done a complete backup, wipe, and restore to reformat the file system. The only way to tell is a tiny one line of text buried in the settings menu. Nothing is shown if it isn't on.Isochronize
Also, legal regulations (HIPAA) require that data is protected. The user should not be able to run the application if data protection is not enabled. It's not up to the user at all, whether he likes it or not. It seems like the unfortunate alternative is going to have to be to implement encryption from scratch.Isochronize
Hi I am in the same situation where I wish to make an enterprise application require that user has set the 4-digit code in order to work. Did you manage to solve this issue?Dunker
I do not agree that your data is not protected at all if no PIN is defined. Data is encrypted using the device key in any case. Still this is considered not to be a real encryption as the passcode is stored somewhere on the device.Hovercraft
Maybe consider changing the answer, since I've proven your accepted answer wrong.Nathan
@Mike's comment "Also, legal regulations (HIPAA) require that data is protected." indicates this is a enterprise or b2b app, which means the device should (and in his case, MUST) be managed - and that is the correct place for data access to be controlled or checked, not in the application logic.Alan
@Mike: "pop up a warning and tell the user that they must enable a passcode and turn on data protection" How was the Apple Review on this case? I mean, was it acceptable to force the user to set a passcode to use your app? Was your app not rejected because of this unusual requirement?Natalia
N
18

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]];
Nathan answered 31/5, 2011 at 19:28 Comment(6)
thanks for taking the time to actually read the question. excellent answerWehrmacht
Are you sure this is working?? I'm testing at the moment with an iPad (4.3.5) and the attribute is always NSFIleProtectionNone... "Data protection is enabled" is showing up in the keycode-settings....Scission
I tested this with 4.3.3 and it worked fine. Feel free to post your code in a different answer if this doesn't work for you.Nathan
this method doesn't work on my iPad 5.0.1 too, always return true.Jann
It looks like this behavior may have changed on iOS 5.0/5.0.1. With the sample code I always get NSFileProtectionNone regardless of the passcode setting on the iPad2.Tempa
Hi,I have tried this one but every time its returning the same value NSFileProtectionKey = NSFileProtectionNone;Augmentative
L
13

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:

  1. Attempt to save a new item on the keychain with that attribute set
  2. If it succeeds that indicates that a passcode is currently enabled
  3. If the password doesn't get saved, that indicates there is no passcode
  4. Cleanup the item, because if it is already on the keychain it will make an "add" fail, looking like the passcode is not set

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

Lamella answered 2/10, 2014 at 2:16 Comment(2)
is this approved by apple ?Excitor
I haven't ever submitted an app that uses it, but all features are documented (no private APIs) so I assume it will be.Lamella
E
3

Apple does not provide a method to determine whether the user has a passcode set.

If your app needs encryption, you should consider encrypting and decrypting the files with a trusted encryption implementation and either prompting the user for a passcode or storing the key in the keychain.

Elda answered 17/7, 2012 at 0:54 Comment(0)
V
1

Regardless NSDataWritingAtomic or NSDataWritingFileProtectionComplete, result is always the same for me. Weird behaviour, here's the code:

BOOL expandTilde = YES;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, expandTilde);
NSString *filePath;
filePath = [[paths lastObject] stringByAppendingPathComponent:@"passcode-check"];

NSMutableData *testData;
testData = [NSMutableData dataWithLength:1024];

NSLog(@"Attempt to write data of length %u file: %@", [testData length], filePath);

NSError *error = nil;

if (![testData writeToFile:filePath options:NSDataWritingAtomic error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return NO;
} else {
    NSLog(@"File write successful.");

    error = nil;
    NSDictionary *testFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];

    NSLog(@"Getting attributes: %@", testFileAttributes);

    if ([NSFileProtectionComplete isEqualToString:[testFileAttributes objectForKey:NSFileProtectionKey]]) {
        error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
        // passcode disabled
        return YES;
    } else {
        error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
        return NO;
    }

} 
Venitavenite answered 17/10, 2011 at 19:13 Comment(2)
By the way the Find Friends App has this functionality. It requests re-entering password only if the device is not password-protected.Venitavenite
Hi,I have checked the link but every time its returning the same value NSFileProtectionKey = NSFileProtectionNone;Augmentative
A
0

Since iOS 9, there is a flag LAPolicyDeviceOwnerAuthentication in LocalAuthentication framework.

+ (BOOL)isPasscodeEnabled
{
    NSError *error = nil;
    LAContext *context = [[LAContext alloc] init];

    BOOL passcodeEnabled = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error];

    if(passcodeEnabled) {
        return YES;
    }

    return NO;
}
Argumentation answered 14/3, 2017 at 17:16 Comment(0)
J
0

Swift 3

func isPasscodeEnabled() -> Bool {
    return LAContext().canEvaluatePolicy(LAPolicy.deviceOwnerAuthentica‌​tion, error:nil)
}

iOS 9 or greater required.

Jemma answered 14/3, 2017 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.