如何确定iPhone用户当前是否设置了密码并启用加密?

时间:2022-11-13 07:33:09

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.

我正在编写一个需要加密数据的iPhone应用程序。我已经学会了如何通过设置NSFileProtectionComplete属性来打开文件加密。我也知道如何检查iPhone版本以确保它们运行的​​是iOS 4.0或更高版本。

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.

我想弹出警告并告诉用户他们必须启用密码并启用数据保护(这需要在4前iPhone上进行备份和恢复),然后如果他们没有密码则退出应用程序并启用数据保护。我无论如何都无法弄清楚这些设置的状态。我发现的所有API,例如UIApplication中的“protectedDataAvailable”,如果禁用数据保护,都会成功通过。

6 个解决方案

#1


18  

Disclaimer: This answer was valid until ios 4.3.3

免责声明:此答案有效期至ios 4.3.3

If data protection is turned on, a newly created file will have a nil NSFileProtectionKey by default.

如果启用了数据保护,则默认情况下新创建的文件将具有nil NSFileProtectionKey。

If data protection is turned off, a newly created file will have a NSFileProtectionNone NSFileProtectionKey by default.

如果关闭数据保护,默认情况下新创建的文件将具有NSFileProtectionNone NSFileProtectionKey。

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]];

#2


13  

iOS 8 (OS X Yosemite) introduced a new API/constant used to detect if a user's device has a passcode.

iOS 8(OS X Yosemite)引入了一个新的API /常量,用于检测用户的设备是否有密码。

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly can be used to detect if a passcode is set on the device.

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly可用于检测设备上是否设置了密码。

The flow is:

流程是:

  1. Attempt to save a new item on the keychain with that attribute set
  2. 尝试使用该属性集在钥匙串上保存新项目
  3. If it succeeds that indicates that a passcode is currently enabled
  4. 如果成功则表示当前启用了密码
  5. If the password doesn't get saved, that indicates there is no passcode
  6. 如果密码未保存,则表示没有密码
  7. Cleanup the item, because if it is already on the keychain it will make an "add" fail, looking like the passcode is not set
  8. 清理项目,因为如果它已经在钥匙串上,它将使“添加”失败,看起来密码未设置

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

我在我的iPhone 5S上测试了这个,首先它返回true,然后我在设置中禁用了密码,它返回false。最后,我重新启用了密码,它返回true。以前的OS版本将返回false。代码在模拟器中工作,在设置了OS X密码的机器上返回true(我没有测试过替代OS X场景)。

Also see sample project here: https://github.com/project-imas/passcode-check/pull/5

另请参阅此处的示例项目: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.

最后,据我所知,iOS 8没有禁用数据保护的设置,因此我认为这就是保证加密所需的全部内容。

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

附:正如Apple在WWDC视频中指出的那样(711 Keychain和使用Touch ID进行身份验证),他们选择不通过API直接提供密码状态,以防止应用程序进入他们不应该进入的情况(即“这个设备是否有密码?好吧,很好,我将以纯文本形式存储此私人信息”。创建加密密钥,将其存储在kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly下并加密该文件会更好,这将是不可恢复的如果用户决定禁用他们的密码)。

#3


3  

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

Apple没有提供确定用户是否设置密码的方法。

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.

如果您的应用需要加密,则应考虑使用可信加密实施来加密和解密文件,并提示用户输入密码或将密钥存储在钥匙串中。

#4


1  

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

无论NSDataWritingAtomic还是NSDataWritingFileProtectionComplete,结果对我来说总是一样的。奇怪的行为,这是代码:

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;
    }

} 

#5


0  

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

从iOS 9开始,LocalAuthentication框架中有一个标志LAPolicyDeviceOwnerAuthentication。

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

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

    if(passcodeEnabled) {
        return YES;
    }

    return NO;
}

#6


0  

Swift 3

斯威夫特3

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

iOS 9 or greater required.

需要iOS 9或更高版本。

#1


18  

Disclaimer: This answer was valid until ios 4.3.3

免责声明:此答案有效期至ios 4.3.3

If data protection is turned on, a newly created file will have a nil NSFileProtectionKey by default.

如果启用了数据保护,则默认情况下新创建的文件将具有nil NSFileProtectionKey。

If data protection is turned off, a newly created file will have a NSFileProtectionNone NSFileProtectionKey by default.

如果关闭数据保护,默认情况下新创建的文件将具有NSFileProtectionNone NSFileProtectionKey。

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]];

#2


13  

iOS 8 (OS X Yosemite) introduced a new API/constant used to detect if a user's device has a passcode.

iOS 8(OS X Yosemite)引入了一个新的API /常量,用于检测用户的设备是否有密码。

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly can be used to detect if a passcode is set on the device.

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly可用于检测设备上是否设置了密码。

The flow is:

流程是:

  1. Attempt to save a new item on the keychain with that attribute set
  2. 尝试使用该属性集在钥匙串上保存新项目
  3. If it succeeds that indicates that a passcode is currently enabled
  4. 如果成功则表示当前启用了密码
  5. If the password doesn't get saved, that indicates there is no passcode
  6. 如果密码未保存,则表示没有密码
  7. Cleanup the item, because if it is already on the keychain it will make an "add" fail, looking like the passcode is not set
  8. 清理项目,因为如果它已经在钥匙串上,它将使“添加”失败,看起来密码未设置

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

我在我的iPhone 5S上测试了这个,首先它返回true,然后我在设置中禁用了密码,它返回false。最后,我重新启用了密码,它返回true。以前的OS版本将返回false。代码在模拟器中工作,在设置了OS X密码的机器上返回true(我没有测试过替代OS X场景)。

Also see sample project here: https://github.com/project-imas/passcode-check/pull/5

另请参阅此处的示例项目: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.

最后,据我所知,iOS 8没有禁用数据保护的设置,因此我认为这就是保证加密所需的全部内容。

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

附:正如Apple在WWDC视频中指出的那样(711 Keychain和使用Touch ID进行身份验证),他们选择不通过API直接提供密码状态,以防止应用程序进入他们不应该进入的情况(即“这个设备是否有密码?好吧,很好,我将以纯文本形式存储此私人信息”。创建加密密钥,将其存储在kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly下并加密该文件会更好,这将是不可恢复的如果用户决定禁用他们的密码)。

#3


3  

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

Apple没有提供确定用户是否设置密码的方法。

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.

如果您的应用需要加密,则应考虑使用可信加密实施来加密和解密文件,并提示用户输入密码或将密钥存储在钥匙串中。

#4


1  

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

无论NSDataWritingAtomic还是NSDataWritingFileProtectionComplete,结果对我来说总是一样的。奇怪的行为,这是代码:

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;
    }

} 

#5


0  

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

从iOS 9开始,LocalAuthentication框架中有一个标志LAPolicyDeviceOwnerAuthentication。

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

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

    if(passcodeEnabled) {
        return YES;
    }

    return NO;
}

#6


0  

Swift 3

斯威夫特3

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

iOS 9 or greater required.

需要iOS 9或更高版本。