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

39
我正在编写一个需要加密数据的iPhone应用程序。我已经学会了如何通过设置NSFileProtectionComplete属性来为文件启用加密功能。同时,我还知道如何检查iPhone的版本,以确保它们运行的是iOS 4.0或更高版本。
但我意识到,如果用户没有选择密码并且没有在“设置”>“通用”>“密码锁定”屏幕上特别启用数据保护,那么数据实际上并没有得到保护。
因此,我想弹出一个警告,并告诉用户他们必须启用密码并打开数据保护(这需要在早期的iPhone上进行备份和恢复),如果他们没有启用密码和数据保护,则退出应用程序。但我找不到任何查找这些设置状态的方法。我发现的所有API(例如UIApplication中的“protectedDataAvailable”)都会在数据保护被禁用时顺利通过。

2
如果没有密码,您的数据在设备上就无法得到保护,因此这对于正在运行的应用程序来说非常重要。如果没有办法确定您的数据是否受到保护,那么苹果公司将犯下一个巨大的失误。这使得通过应用商店销售的大多数企业应用程序而言,新的iOS 4加密功能几乎毫无用处。 - Mike
3
大多数企业都会(应该)推送部署配置文件到公司所有的iPhone上,以要求设置密码。这不是程序问题,而是管理问题。你真的想弹出一个警告告诉用户开启他的密码吗?或者更好的方式是拒绝运行,除非密码已经设置?用户通常不喜欢被告知如何使用他们的设备。 - Jon Shier
2
通过应用商店销售的应用程序无法拥有部署配置文件。此应用程序不适用于公司管理的手机。当前的安全模型对用户来说很困惑,他们很难确定自己的数据是否安全。例如,如果您有一台iPhone 3GS,即使您已升级到iOS 4并开启了密码,除非您进行完整的备份、擦除和恢复以重新格式化文件系统,否则您的数据是不受保护的。唯一的方法是在设置菜单中找到一个微小的文本行。如果它没有打开,就什么也不会显示。 - Mike
2
此外,法律法规(HIPAA)要求保护数据。如果未启用数据保护,则用户不应能够运行应用程序。无论用户是否喜欢,这都不取决于用户。看起来不幸的选择将不得不从头开始实现加密。 - Mike
也许考虑修改答案,因为我已经证明了你接受的答案是错误的。 - Heath Borders
显示剩余5条评论
6个回答

18

免责声明:本答案适用于ios 4.3.3及以下版本。

如果数据保护被打开,一个新创建的文件将默认拥有nilNSFileProtectionKey

如果数据保护被关闭,一个新创建的文件将默认拥有NSFileProtectionNoneNSFileProtectionKey

因此,您可以使用以下代码检测文件保护的存在:

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

感谢您抽出时间认真阅读问题并提供了优秀的答案。 - Jason Cragun
1
你确定它能正常工作吗?我正在使用 iPad(4.3.5)进行测试,但属性始终是NSFileProtectionNone...“数据保护已启用”显示在键代码设置中... - The-MeLLeR
1
我在4.3.3上测试过了,运行正常。如果这对你不起作用,可以在另一个答案中发布你的代码。 - Heath Borders
3
这种方法在我的iPad 5.0.1上也不起作用,总是返回true。 - Andy
2
看起来这种行为在iOS 5.0/5.0.1上可能已经改变了。使用示例代码,无论iPad2上的密码设置如何,我始终得到NSFileProtectionNone。 - thrusty
嗨,我已经尝试过这个了,但每次它都返回相同的值NSFileProtectionKey = NSFileProtectionNone;。 - Suchi

13
iOS 8(OS X Yosemite)引入了一种新的API/常量,用于检测用户设备是否设置了密码。
可以使用kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly来检测设备上是否设置了密码。
流程如下:
1. 尝试使用该属性在钥匙串中保存新项目 2. 如果成功,表示当前启用了密码 3. 如果密码未被保存,则表示没有密码 4. 清理项目,因为如果它已经在钥匙串中,则会使“添加”操作失败,看起来好像密码未设置
我在我的iPhone 5S上测试过这个方法,首先返回true,然后我在设置中禁用了密码,它返回false。最后,我重新启用了密码,它返回true。之前的操作系统版本将返回false。该代码在模拟器中也可以工作,在安装有OS X密码的机器上返回true(我没有测试其他OS X场景)。
此外,请参见此处的示例项目:https://github.com/project-imas/passcode-check/pull/5 最后,据我所知,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.正如苹果在介绍WWDC视频(711钥匙链和使用Touch ID进行身份验证)中指出的那样,他们故意选择不直接通过API公开密码状态,以防止应用程序陷入不该有的情况(例如:“这个设备有密码吗?好的,我将以明文形式存储这些私人信息”)。更好的做法是创建一个加密密钥,将其存储在kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly下,并加密该文件。如果用户决定禁用他们的密码,则无法恢复该文件。


这个是否被苹果公司批准了? - nr5
我从未提交过使用它的应用程序,但所有功能都已记录(没有私有 API),因此我认为会成功。 - owenfi

3

苹果不提供确定用户是否设置了密码的方法。

如果您的应用程序需要加密,您应该考虑使用可信任的加密实现对文件进行加密和解密,并提示用户输入密码或将密钥存储在钥匙串中。


1
无论是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;
    }

} 

2
顺便提一下,“查找好友”应用程序具有此功能。如果设备没有设置密码保护,它会要求重新输入密码。 - igraczech
嗨,我已经检查了链接,但每次它都返回相同的值NSFileProtectionKey = NSFileProtectionNone;。 - Suchi

0

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

0

迅速 3

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

需要 iOS 9 或更高版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接