39

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

我已经意识到,如果用户没有选择密码并且没有在“设置”>“常规”>“密码锁定”屏幕上专门启用数据保护,那么数据实际上根本没有受到保护。

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

4

6 回答 6

18

免责声明:此答案在 ios 4.3.3 之前有效

如果打开数据保护,新创建的文件将nil NSFileProtectionKey默认有一个。

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

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

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]];
于 2011-05-31T19:28:05.150 回答
13

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

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

流程是:

  1. 尝试使用该属性集在钥匙串上保存新项目
  2. 如果成功,则表明当前启用了密码
  3. 如果密码没有保存,则表示没有密码
  4. 清理该项目,因为如果它已经在钥匙串上,它将使“添加”失败,看起来好像没有设置密码

我在我的 iPhone 5S 上对此进行了测试,首先它返回true了,然后我在设置中禁用了密码,然后它返回了false。最后,我重新启用密码并返回true. 以前的操作系统版本将返回false。该代码在模拟器中运行,true在设置了 OS X 密码的机器上返回(我还没有测试过其他 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;

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

于 2014-10-02T02:16:00.757 回答
3

Apple 没有提供一种方法来确定用户是否设置了密码。

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

于 2012-07-17T00:54:23.717 回答
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;
    }

} 
于 2011-10-17T19:13:16.350 回答
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;
}
于 2017-03-14T17:16:47.853 回答
0

斯威夫特 3

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

需要 iOS 9 或更高版本。

于 2017-03-14T18:41:54.013 回答