Ios SecItemAdd()在未锁定时成功使用ksecattraccessibleewhen,但仅在未锁定此设备时失败使用ksecattraccessibleewhen 到目前为止的故事

Ios SecItemAdd()在未锁定时成功使用ksecattraccessibleewhen,但仅在未锁定此设备时失败使用ksecattraccessibleewhen 到目前为止的故事,ios,keychain,Ios,Keychain,四个月前,我发布了一篇帖子,因为升级到iOS 13破坏了我的钥匙链相关代码 My code使用classkSecClassGenericPassword和access属性ksecattracccessiblewhen unlocked将用户密码存储在钥匙链中。正如我在回答这个问题时所解释的那样,通过稍微清理一下查询字典,我最终使我的代码也能在iOS 13上工作 当前的问题 几周前,我被要求禁用密码数据备份以增强安全性,因此我将访问属性更改为ksecataccessiblewhenunlocked

四个月前,我发布了一篇帖子,因为升级到iOS 13破坏了我的钥匙链相关代码

My code使用class
kSecClassGenericPassword
和access属性
ksecattracccessiblewhen unlocked
将用户密码存储在钥匙链中。正如我在回答这个问题时所解释的那样,通过稍微清理一下查询字典,我最终使我的代码也能在iOS 13上工作

当前的问题 几周前,我被要求禁用密码数据备份以增强安全性,因此我将访问属性更改为
ksecataccessiblewhenunlockedthisdeviceonly
(与未锁定时的可访问性不同,钥匙链中的密码在备份期间不会传输到另一个设备)

现在,我的代码失败,用户每次都必须输入密码。(在iOS 13.0、iPhone 8 Plus上测试)

当用户使用其密码登录时,“我的代码”首先使用
SecItemDelete()
删除以前存储的任何密码,然后使用
secitemdeach()
继续存储输入的密码

由于将访问属性更改为“仅当未锁定此设备时才可访问”
ksecattracessible
SecItemDelete()
“成功”的是
errSecItemNotFound
(即“无需删除”),但是
secitemdadd()
失败的是
errSecDuplicateItem

注意,这不是试图检索以前使用
ksecattraccessibleewhenunlocked
存储的密码的问题(即,存储和加载的不同查询字典);我从设备中删除了应用程序,并从一开始就尝试使用新代码和
secitemdadd()
总是失败


发生了什么事?

我想你已经明白了,但为了完整起见:

注意,这不是试图检索以前使用ksecataccessiblewhen未锁定时使用ksecataccessiblewhen未锁定此设备时使用ksecataccessiblewhen存储的密码的问题(即,存储和加载的不同查询字典);我从设备中删除了应用程序,并从一开始就尝试使用新代码,SecItemAdd()总是失败


这正是发生在你身上的事情。钥匙链内容在应用程序删除和重新安装后仍然有效。要解决此问题,你需要提出一种迁移策略,在尝试保存新项目之前,先删除使用相同主键但不同访问控制设置保存的项目,如:“kSecAttractAccessibleAllwaysThisDeviceOnly”或“ksecAttractAccessibleAllways”,则处理保存在密钥链中的项目的最佳方法是使用迁移策略来转换使用这些不推荐的密钥保存的密钥链项目,以便使用新密钥(如“ksecAttractAccessibleAfterFirstUnlockThisDeviceOnly”转换/保存/复制它们

没有更好的方法,因为钥匙链项目总是在应用程序删除和重新安装后仍然有效。

以下是有效的代码: 注意:应为所有键触发(对于每个键/值对)

注意:一旦执行上述代码,使用这些旧的/不推荐的可访问密钥保存的密钥/值对将被删除,并且只有使用新的可访问密钥更新的密钥/值对将存在于密钥链中


希望,这有助于挽救某人的一天!

谢谢,这正是我现在正在做的。我的印象是,在最近一次失败的iOS版本中,“修复”了密钥链数据幸存安装(没有记录,因为这显然是一个实现细节,而不是规范的一部分)(不记得是哪个…11?)是的,现在我正在检索这两个值,并存储我真正想要的值(迁移)。谢谢你,我没有意识到有人反对!
+ (void)updateNewchainDataForKey:(NSString*)key {
    
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];

//Our OLD keychain items were saved with 'kSecAttrAccessibleAlwaysThisDeviceOnly'(DEPRECATED) accessible key
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];

[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];


CFDictionaryRef resultDataRef = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, (CFTypeRef *)&resultDataRef);
NSDictionary *resultDict = (__bridge_transfer NSDictionary *)resultDataRef;

NSLog(@"resultDict %@", resultDict);

if( status != errSecSuccess) {
    NSLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
    return;
}

if (status == errSecSuccess && resultDict) {
    
    // Check if we have the old attribute type(s)
    if ([[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlways)]
        || [[[resultDict objectForKey:(__bridge id)kSecAttrAccessible] copy] isEqualToString:(__bridge NSString *)(kSecAttrAccessibleAlwaysThisDeviceOnly)]) {
        
        // Update the deviceID attribute to kSecAttrAccessibleAlwaysThisDeviceOnly
        NSMutableDictionary *updateQuery = [NSMutableDictionary dictionary];
        
        //Our keychain items are now being saved with 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly' accessible key
        // Set the new attribute
        [updateQuery setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
        
        NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
        [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
        
        NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
        [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
        [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
        
        // Perform the update
        OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dict, (__bridge CFDictionaryRef)updateQuery);
        if (status != errSecSuccess) {
            NSLog(@"status failed %d", (int)status);
        } else {
            NSLog(@"status PASS %d", (int)status);
        }
        
    }
    
  }
}